What is the difference between ObservedObject and StateObject in SwiftUI
IosSwiftSwiftuiIos Problem Overview
If I have an ObservableObject
in SwiftUI I can refer to it as an @ObservedObject
:
class ViewModel: ObservableObject {
@Published var someText = "Hello World!"
}
struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
var body: some View {
Text(viewModel.someText)
}
}
Or as a @StateObject
:
class ViewModel: ObservableObject {
@Published var someText = "Hello World!"
}
struct ContentView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
Text(viewModel.someText)
}
}
But what's the actual difference between the two? Are there any situations where one is better than the other, or they are two completely different things?
Ios Solutions
Solution 1 - Ios
@ObservedObject
When a view creates its own @ObservedObject
instance it is recreated every time a view is discarded and redrawn:
struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
}
On the contrary a @State
variable will keep its value when a view is redrawn.
@StateObject
A @StateObject
is a combination of @ObservedObject
and @State
- the instance of the ViewModel
will be kept and reused even after a view is discarded and redrawn:
struct ContentView: View {
@StateObject var viewModel = ViewModel()
}
Performance
Although an @ObservedObject
can impact the performance if the View is forced to recreate a heavy-weight object often, it should not matter much when the @ObservedObject
is not complex.
When to use @ObservedObject
It might appear there is no reason now to use an @ObservedObject
, so when should it be used?
> You should use @StateObject for any observable properties that you > initialize in the view that uses it. If the ObservableObject instance > is created externally and passed to the view that uses it mark your > property with @ObservedObject.
Note there are too many use-cases possible and sometimes it may be desired to recreate an observable property in your View. In that case it's better to use an @ObservedObject
.
Useful links:
Solution 2 - Ios
Apple documentation did explain why initializing with ObservedObject
is unsafe.
> SwiftUI might create or recreate a view at any time, so it’s important that initializing a view with a given set of inputs always results in the same view. As a result, it’s unsafe to create an observed object inside a view.
The solution is StateObject
.
At the same time, the documentation showed us how we should create data models in a view (or app/scene) when it can hold on to the truth, and pass it to another view.
struct LibraryView: View {
@StateObject var book = Book() // Hold on to the 1 truth
var body: some View {
BookView(book: book) // Pass it to another view
}
}
struct BookView: View {
@ObservedObject var book: Book // From external source
}
Solution 3 - Ios
Even though pawello2222's answer have nicely explained the differences when the view itself creates its view model, it's important to note the differences when the view model is injected into the view.
When you inject the view model into the view, as long as the view model is a reference type, there are no differences between @ObservedObject
and @StateObject
, since the object that injected the view model into your view should hold a reference to view model as well, hence the view model isn't destroyed when the child view is redrawn.
class ViewModel: ObservableObject {}
struct ParentView: View {
@ObservedObject var viewModel = ViewModel()
var body: some View {
ChildView(viewModel: viewModel) // You inject the view model into the child view
}
}
// Even if `ChildView` is discarded/redrawn, `ViewModel` is kept in memory, since `ParentView` still holds a reference to it - `ViewModel` is only released and hence destroyed when `ParentView` is destroyed/redrawn.
struct ChildView: View {
@ObservedObject var viewModel: ViewModel
}
Solution 4 - Ios
Here is an example to illustrate the difference.
Every time you click the Refresh
button the StateObjectClass
is recreated from scratch only for CountViewObserved
. This means it's @Published
count
property gets the default value of 0
when this happens.
The difference between @StateObject
and @ObservedObject
is clear. The @StateObject
version of the observed StateObjectClass
preserves its state since it is never deinitted. The @ObservedObject
version does not as it is recreated. So you should use @StateObject
for the owner of an ObservableObject
.
import SwiftUI
class StateObjectClass: ObservableObject {
enum ObserverType: String {
case stateObject
case observedObject
}
@Published var count = 0
let type: ObserverType
let id = UUID()
init(type: ObserverType) {
self.type = type
}
deinit {
print(#function, "type: \(type.rawValue) id: \(id)")
}
}
struct CountViewState: View {
@StateObject var state = StateObjectClass(type: .stateObject)
var body: some View {
VStack {
Text("@StateObject's count: \(state.count)")
Button("ADD 1"){
state.count += 1
}
}
}
}
struct CountViewObserved: View {
@ObservedObject var state = StateObjectClass(type: .observedObject)
var body: some View {
VStack {
Text("@ObservedObject's count: \(state.count)")
Button("Add 1") {
state.count += 1
}
}
}
}
struct ContentView: View {
@State private var count = 0
var body: some View {
VStack {
Text("Refresh CounterView's count: \(count)")
Button("Refresh") {
count += 1
}
CountViewState()
.padding()
CountViewObserved()
.padding()
}
}
}
Solution 5 - Ios
@StateObject
is a state of a given view, thus the instance of it is retained by SwiftUI across body
updates. It is not retained though when running in Preview.
@ObservedObject
on the other hand is just an object being observed by given View
, thus is not retained by SwiftUI (it has to be retained outside of the View
).
In other words - it looks like SwiftUI keeps a strong
reference of @StateObject
and unowned
reference of @ObservedObject
.
Retained vs non-retained source, Previews behavior source, around ~8:30.
Solution 6 - Ios
The difference between let's say :
@ObservedObject var book: BookModel
And
@StateObject var book: BookModel
@ObservedObject
does NOT own the instance book
, its your responsibility to manage the life cycle of the instance..
But when you want to tie the life cycle of your observable object book
to your view like in @State
you can use @StateObject
.
In this case SwiftUI will OWN the observable object and the creation and destruction will be tied to the view's life cycle
SwiftUI will keep the object alive for the whole life cycle of the view
This is great for expensive resources, you do not need to fiddle with onDisappear
anymore to release resources.
This clarification is taken from WWDC2020 Data essentials in SwiftUI