read

I wrote on StackOverflow with a piece of code on how to observe an @Published.

class MyValue: ObservableObject {
    @Published var value: String = ""
    init() {
        $value.sink { ... }
    }
}

The code works, but might not be as intended.

In the sink, if you print out value, it will NOT be the new value. It will be the old value, before the change.

The subscriber is getting the sink when the value willChange, NOT didChange.

That’s how ObservableObject works. But in SwiftUI early beta, it was once designed to emit after the change, with objectDidChange.

Why SwfitUI emit BEFORE the change?

Why did the design changed?

It was discussed here. In this tweet they mention the reason:

We made the change because it allows us to do a better job in coalescing updates; especially in the presence of animations.

For the sake of them doing a better job, optimizing for animations, they made the change.

It is a pitfall, so we need workarounds.

Solution 1

The easiest way is to use a dispatch call.

$value.sink { [weak self] in
    DispatchQueue.main.async { [weak self] in
        ...
    }
}

That usually works.

Solution 2

The more full proof way is to have a separate event being emitted when the @Published is didSet:

class MyValue: ObservableObject {
    var valueDidChange = PassthroughSubject<Void, Never>()

    @Published var value: String = "" {
        didSet {
            valueDidChange.send()
        }
    }
}

Then subscribe to valueDidChange instead.

$valueDidChange.sink { [weak self] in
    ...
}

Solution 3

You may not need to access the actual value in the object. In that case, you can simply observe and use the new value given in the sink closure:

$value.sink { newValue in
    // Use newValue, instead of value
}

Image

@samwize

¯\_(ツ)_/¯

Back to Home