SwiftUI: Differences between @ObservedObject
vs @StateObject
To learn the difference between @ObservedObject
and @StateObject
, the draft by Chris Eidhof from objc.io nicely summarizes the difference quite nicely. (original tweet). @StateObject
is used when the object is initialized by the view, and @ObservedObject
is used when an object is passed as a parameter.
Let’s get down to test case. To start, let’s create simple object
class TestObject: ObservableObject {
let title: String
@Published var num: Int = 0 init(title: String) {
self.title = title
print("\(title) Init")
} deinit {
print("-->>\(title) deInit")
}}
This TestObject
have two properties, title
as an identification, and num
to store int. We print the object title during initialize and deinitialize to determine the lifecycle of the object.
Now for the view
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink("to Main View", destination: MainView())
}.navigationViewStyle(StackNavigationViewStyle())
}
}
From ContentView we navigate to MainView
struct MainView: View { @ObservedObject var observedObject = TestObject(title: "ObservedObj MainView") @StateObject var stateObject = TestObject(title: "StateObj MainView") var body: some View {
VStack {
Text("\(observedObject.title): \(observedObject.num)")
Button("Increase \(observedObject.title)", action: {
observedObject.num += 1
print("\(observedObject.title): \(observedObject.num)")
}) Text("\(stateObject.title): \(stateObject.num)")
Button("Increase \(stateObject.title)", action: {
stateObject.num += 1
print("\(stateObject.title): \(stateObject.num)")
})
}
}
}
In MainView, we use both @ObservedObject
and @StateObject
to see the difference between those two. We put button to count the number for each object. When we build the project we notice
As you can see, even though we’re not in the MainView yet, the “ObservedObj MainView” object is already initiated, while “StateObj MainView” is not initiated yet. @ObservedObject makes the object initiated before the view is called, and @StateObject only create the object when the view is called.
From the gif above, we can see that object with property wrapper @StateObject only initiated when the view is called.
If we change the properties in both “ObservedObj MainView” and “StateObj MainView” and we return to parent view, the @StateObject object is deinitialized when we return to parent view, causing the need to initialized the object again when we return to the MainView. But in @ObservedObject object, it’s still storing the data because the object is not deinitialized yet.
struct MainView: View {@ObservedObject var observedObject = TestObject(title: "ObservedObj MainView")@StateObject var stateObject = TestObject(title: "StateObj MainView")var body: some View {
VStack {
Text("\(observedObject.title): \(observedObject.num)")
Button("Increase \(observedObject.title)", action: {
observedObject.num += 1
print("\(observedObject.title): \(observedObject.num)")
}) Text("\(stateObject.title): \(stateObject.num)")
Button("Increase \(stateObject.title)", action: {
stateObject.num += 1
print("\(stateObject.title): \(stateObject.num)")
})
NavigationLink("To ChildView", destination: ChildView())
}
}
}
In the ChildView, we initialize another @ObservedObject and @StateObject
struct ChildView: View {@ObservedObject var observedObject = TestObject(title: "ObservedObj ChildView")@StateObject var stateObject = TestObject(title: "StateObj ChildView")var body: some View {
VStack {
Text("\(observedObject.title): \(observedObject.num)")
Button("Increase \(observedObject.title)", action: {
observedObject.num += 1
print("\(observedObject.title): \(observedObject.num)")
})
Text("\(stateObject.title): \(stateObject.num)")
Button("Increase \(stateObject.title)", action: {
stateObject.num += 1
print("\(stateObject.title): \(stateObject.num)")
})
}
}
}
let’s see the difference between those two
As you can see, in ContentView, only “ObservedObj MainView” initialized, but the @ObservedObject object in ChildView is not initialized yet. When the MainView is called, “StateObj MainView” is initialized. “ObservedObj ChildView” is also initialized in MainView because the next navigation link is calling the ChildView.
Because “ObservedObj ChildView” is not initialized until MainView is called, That object is deinitialized when we return to ContentView.
Endnote
To conclude my little experiment, using the appropriate property wrapper is important so your apps won’t initialize unused object. The best practice is to initialize the object when the view who used it is called using @StateObject property wrapper. Use @ObservedObject in the child view when the object is passed down from the parent view to child view as a parameter.