tl;dr
There are a variety of ways to trigger the @Observable
event without changing the value, but if one is observing these events in SwiftUI, you may not see this event. SwiftUI checks the “before” and “after” values to see if the view needs to be rerendered or not (as part of its broader observation coalescing optimizations). The events will happen, but SwiftUI may optimize them away. You may want to consider other patterns to trigger your desired behavior when the actual values do not change.
If you are interesting in seeing what the mutation of an observed property in an @Observable
type is doing behind the scenes, you can expand the macro. So consider:
@Observable
final class MyState<T> {
var state: T
init(state: T) {
self.state = state
}
}
I can control-clicking (or right-clicking or choosing “Expand Macro” from Xcode’s “Editor” menu) on the @Observable
keyword and choose “Expand Macro`:
And after repeating that on @ObservationTracked
, we see:
That shows us exactly what a mutation of the observed property does:
That would imply that one might call withMutation(of:keyPath:_:)
as shown in the expanded macro:
func triggerUpdate() {
_$observationRegistrar.withMutation(of: self, keyPath: \.state) { }
}
But I wouldn't recommend using _
prefixed properties, as that is exceptionally brittle, so perhaps one might leverage the withMutation
function created by the macro:
func triggerUpdate() {
withMutation(keyPath: \.state) { }
}
But even that strikes me as a little brittle (given that the documentation makes no assurances as to what methods the @Observable
macro provides). I hesitate to rely upon macro expansions that are not formally documented. So, I might instead suggest:
func triggerUpdate() {
state = state
}
All of those trigger the observation notification. But, all of that having been said, while the above is interesting, if one is observing this in SwiftUI (which is the primary use-case for the Observation framework), one should note that SwiftUI will compare before and after values to see if the view needs to be re-rendered, at all. If the value is the same, it will neither re-render the view nor trigger any of the onChange
or task
view modifiers.
When I verified the above this with withObservationTracking
, I saw that all of the above alternatives actually triggered the expected observation notification. But if one’s intent is to trigger some SwiftUI event, its optimizations may thwart the attempt. Other patterns for triggering re-rendering of views is called for.
Alternatives include:
- observe something that actually has changed (which is presumably why the UI needs to be re-rendered, to show some change),
- an
AsyncSequence
for events,
- posting custom
Notification
to the NotificationCenter
,
- adding something (like a timestamp or count) that will change even if the value hasn’t, etc.