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
    ...
}

Image

@samwize

¯\_(ツ)_/¯

Back to Home