SwiftUI: Differences between @ObservedObject vs @StateObject

Tweet by Chris Eidhof from objc.io

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.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store