Is there an alternative to Combine's @Published that signals a value change after it has taken place instead of before?

You can write your own custom property wrapper:

import Combine


@propertyWrapper
class DidSet<Value> {
    private var val: Value
    private let subject: CurrentValueSubject<Value, Never>

    init(wrappedValue value: Value) {
        val = value
        subject = CurrentValueSubject(value)
        wrappedValue = value
    }

    var wrappedValue: Value {
        set {
            val = newValue
            subject.send(val)
        }
        get { val }
    }

    public var projectedValue: CurrentValueSubject<Value, Never> {
      get { subject }
    }
}

Before the introduction of ObservableObject SwiftUI used to work the way that you specify - it would notify you after the change has been made. The change to willChange was made intentionally and is probably caused by some optimizations, so using ObservableObjsect with @Published will always notify you before the changed by design. Of course you could decide not to use the @Published property wrapper and implement the notifications yourself in a didChange callback and send them via objectWillChange property, but this would be against the convention and might cause issues with updating views. (https://developer.apple.com/documentation/combine/observableobject/3362556-objectwillchange) and it's done automatically when used with @Published. If you need the sink for something else than ui updates, then I would implement another publisher and not go agains the ObservableObject convention.


Further to Eluss's good explanation, I'll add some code that works. You need to create your own PassthroughSubject to make a publisher, and use the property observer didSet to send changes after the change has taken place.

import Combine

class A {
    public var fooDidChange = PassthroughSubject<Void, Never>()

    var foo = false { didSet { fooDidChange.send() } }
}

let a = A()
let fooSink = a.fooDidChange.sink { _ in
    print("foo is now \(a.foo)")
}

a.foo = true

Tags:

Swift

Combine