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
}