Observation vs. StateObject in SwiftUI
The confusion
iOS 17 made Observation feel like the answer to a lot of old SwiftUI pain.
The wrappers got lighter, child editing got easier, and some models that once needed @StateObject can now live comfortably inside @State.
That can lead to a false conclusion: if Observation is modern, then @StateObject must be legacy.
It is not that simple.
The real decision is still about ownership, not trendiness. If you ask the wrong question, you end up replacing one wrapper with another and still not understanding why a screen behaves the way it does.
What SwiftUI is actually doing
Observation tells SwiftUI when properties change.
@StateObject tells SwiftUI to keep a reference-type owner alive across view updates.
Those are related, but they are not the same job.
In a simple editing screen, @State plus @Observable is often enough:
the screen creates the model, children borrow access, and SwiftUI redraws when values change.
In a more involved feature, @StateObject can still be useful because the model needs explicit lifetime management, side effects, or compatibility with existing code.
That is why “what compiles” is not the same as “what communicates intent.”
The mental model
Observation is about change tracking. @StateObject is about long-lived ownership.
If you keep that split clear, the choice becomes easier:
- use
@Observablewhen the model is part of a SwiftUI-native ownership flow - use
@StateObjectwhen the view must keep a reference-type owner stable across recomputations - use
@Bindablewhen a child should edit without owning
The wrapper is not the architecture. The wrapper expresses the architecture you already chose.
A small proof
Consider a profile editor. The screen owns a draft and a nested form edits it:
@Observable
final class ProfileDraft {
var displayName = "Pascal"
}
struct ProfileScreen: View {
@State private var draft = ProfileDraft()
var body: some View {
ProfileEditor(draft: draft)
}
}
That is a clean Observation fit because the screen owns the draft and the child only edits fields.
Now compare it to a caching object or service that performs work when the view appears.
That object often benefits from a stable owner, especially if its lifecycle should survive redraws and view recomputation.
In that case, @StateObject can still be the more honest expression of intent.
Why this matters in real apps
The practical impact shows up in refactors.
If you move a form into a reusable component, Observation keeps the bindings light.
If you move a screen that manages a long-lived loader or subscription, @StateObject may keep the lifecycle clearer.
It also affects team communication.
When the code uses @Observable everywhere, readers may assume the model is cheap to recreate or safe to share anywhere.
When the code uses @StateObject everywhere, readers may assume the feature needs more lifecycle than it really does.
Both habits hide the real trade-off.
Two concrete examples make the difference clearer:
- A draft form for profile editing usually fits Observation because the screen owns the draft and child views only edit it.
- A dashboard service that manages loading state, retries, and caching often wants
@StateObjectbecause lifetime matters more than binding convenience.
Where this model breaks down
The boundary is not always clean. Mixed codebases often contain both wrappers during migration, and that is okay. The mistake is not coexistence. The mistake is losing track of why each wrapper exists.
Another edge case is overusing @Observable for data that really belongs in environment or a dependency container.
If every view can mutate everything, the code gets easy to wire but hard to reason about.
So the rule is not “replace @StateObject with @Observable.”
The rule is “pick the wrapper that matches the ownership story you want the code to tell.”
One sentence to remember
Use Observation for change tracking and @StateObject for long-lived ownership.
Next steps
If you want to keep that boundary crisp, these are the best follow-ups: