@StateObject vs @ObservedObject in SwiftUI: The Difference That Actually Matters
Both update the UI, but only @StateObject owns the object’s lifetime. Choose wrong, and your state can reset.
@StateObject vs @ObservedObject in SwiftUI: The Difference That Actually Matters
Both update the UI, but only @StateObject owns the object’s lifetime. Choose wrong, and your state can reset.

Image generated using AI
Not a Medium member? Use this link for free access.
Hola Swifties,
When I started learning and building with SwiftUI, a lot of property wrappers confused me — @State, @Binding, @StateObject, @ObservedObject, and many more.
But the one confusion that stayed with me for a long time was the difference between @StateObject and @ObservedObject.
At first, both looked almost the same.
Both can observe an ObservableObject.
Both can update the UI when a @Published property changes.
Both are mostly used with view models.
So the real question was:
If both can update the UI, then why do we even need two different property wrappers?
The answer is ownership.
@ObservedObject observes changes.
@StateObject observes changes and owns the object’s lifetime.
And this is the difference that actually matters.
Because if you choose the wrong one, your view model can get recreated, your state can reset, and you may start debugging a problem that looks like a SwiftUI bug — but is actually an ownership mistake.
In this article, let’s break down what @StateObject and @ObservedObject actually do, where developers usually misuse them, and how to decide which one to use in real SwiftUI code.
Before going deeper, let’s first look at what Apple’s official documentation says about both.
StateObject — A property wrapper type that instantiates an observable object.
ObservedObject — A property wrapper type that subscribes to an observable object and invalidates a view whenever the observable object changes.
In simple terms:
**@StateObject** is for the object that this view creates and owns.
**@ObservedObject** is for the object that this view receives from somewhere else and only observes.
That is the real difference.
Both can update the UI when a published value changes, but only @StateObject is responsible for keeping the object alive for the lifetime of the view.
The Common Mistake: Creating an Object with @ObservedObject
This is where most of the confusion starts.
Many developers write something like this:
final class DataModel: ObservableObject {
@Published var count = 0
init() {
print("DataModel initialized")
}
}
struct CounterView: View {
@ObservedObject private var model = DataModel()
var body: some View {
VStack(spacing: 16) {
Text("Count: \(model.count)")
Button("Increment") {
model.count += 1
}
}
}
}
At first, this looks completely fine.
The UI updates when count changes.
The button works.
The view reacts to @Published.
So what is the problem?
The problem is not UI updates.
The problem is ownership.
Here, CounterView is creating the object:
DataModel()
But it is using @ObservedObject, which means:
“I am only observing this object. I am not responsible for owning its lifetime.”
That is the mismatch.
If the view creates the object, the view should own it. And in SwiftUI, that ownership should be expressed using @StateObject.
So this is the better version:
struct CounterView: View {
@StateObject private var model = DataModel()
var body: some View {
VStack(spacing: 16) {
Text("Count: \(model.count)")
Button("Increment") {
model.count += 1
}
}
}
}
Now the meaning is clear:
@StateObject private var model = DataModel()
This says:
“This view creates the object, and SwiftUI should keep it alive as long as this view identity exists.”
That is the difference that actually matters.
@ObservedObject is not wrong because it cannot update the UI.
It is wrong here because the object is being created in the same view that only claims to observe it.
Remember this :
@ObservedObjectanswers “who should I listen to?”@StateObjectanswers “who owns this object?”
OR
If your view creates the view model, use
@StateObject. If your view receives the view model, use@ObservedObject.
The Correct Pattern: Parent Owns, Child Observes
In real apps, we usually do not keep everything inside one view.
A screen may have a parent view, header view, list view, footer view, loading state view, error view, and many small reusable components.
This is where the ownership rule becomes very useful.
The parent screen should create and own the view model using @StateObject.
The child views should receive the same view model and observe it using @ObservedObject.
final class ProfileViewModel: ObservableObject {
@Published var name = "Billie Eilish"
@Published var isLoading = false
func refresh() {
isLoading = true
// API call or business logic
}
}
Now the parent owns the object:
struct ProfileScreen: View {
@StateObject private var viewModel = ProfileViewModel()
var body: some View {
VStack(spacing: 16) {
ProfileHeaderView(viewModel: viewModel)
ProfileActionView(viewModel: viewModel)
}
}
}
And the child views only observe it:
struct ProfileHeaderView: View {
@ObservedObject var viewModel: ProfileViewModel
var body: some View {
Text(viewModel.name)
.font(.headline)
}
}
struct ProfileActionView: View {
@ObservedObject var viewModel: ProfileViewModel
var body: some View {
Button("Refresh") {
viewModel.refresh()
}
}
}
Now the ownership is clear.
ProfileScreen creates the view model, so it uses @StateObject.
ProfileHeaderView and ProfileActionView receive the view model from outside, so they use @ObservedObject.
This is the pattern I personally find easiest to remember:
Parent owns
@StateObject. Child observes with@ObservedObject.
This also avoids creating multiple instances of the same view model by mistake.
Because if every child starts using @StateObject, then every child may end up owning its own separate object. That is not shared state anymore. That is duplicated state.
So the question is not just:
“Will this update the UI?”
The better question is:
“Who should own this object’s lifetime?”
Once you answer that, choosing between **@StateObject and `@ObservedObject`** becomes much easier.
This ownership problem is also connected to how SwiftUI thinks about view lifecycle and state changes. I wrote more about that here: Stop Using onAppear for API Calls: Master the SwiftUI State Machine
Final Rule
The easiest way to decide between @StateObject and @ObservedObject is to ask one question:
Who owns this object?
If the view creates the object, use @StateObject.
@StateObject private var viewModel = ProfileViewModel()
If the view receives the object from somewhere else, use @ObservedObject.
@ObservedObject var viewModel: ProfileViewModel
That is the difference that actually matters.
Both can update the UI when a @Published property changes. But @StateObject is responsible for keeping the object alive for the lifetime of the view identity. @ObservedObject only listens to an object that is already owned somewhere else.
So next time you are confused, don’t start with:
“Which one updates the UI?”
Start with:
“Who should own this object’s lifetime?”
And one small thing to think about:
We usually write @StateObject like this:
@StateObject private var viewModel = ProfileViewModel()
But why do we keep it **private**?
That is not just a syntax choice. It is also an ownership signal.
if this view owns the object, outside views should not directly mutate or replace it. The view can pass it down to child views, but the source of truth should stay protected inside the owner.
Maybe that deserves a separate article.
What do you think? Should @StateObject always be private when the view owns it?
Read More
If you found this useful, you may also like these SwiftUI articles:
메타데이터
- post_id
- 5705d3fa29c3
- slug
- stateobject-vs-observedobject-in-swiftui-the-difference-that-actually-matters-5705d3fa29c3
- url
- https://medium.com/@rushikeshsuradkar2000/stateobject-vs-observedobject-in-swiftui-the-difference-that-actually-matters-5705d3fa29c3
- canonical_url
- https://medium.com/@rushikeshsuradkar2000/stateobject-vs-observedobject-in-swiftui-the-difference-that-actually-matters-5705d3fa29c3
- author_url
- https://medium.com/@rushikeshsuradkar2000
- status
- ok
- fetched_at
- 2026-06-09 15:37:30