What is the difference between ObservedObject and StateObject in SwiftUI
@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:
- What’s the difference between @StateObject and @ObservedObject?
- What’s the difference between @ObservedObject, @State, and @EnvironmentObject?
- What is the @StateObject property wrapper?
@StateObject vs @ObservedObject when passed externally but owned by the view
This is a really interesting question. There's some subtle behavior going on here.
First, notice that you can't just change @ObservedObject
to @StateObject
in NameView
. It won't compile:
struct NameView: View {
@StateObject var viewModel: ViewModel
init(_ viewModel: ViewModel) {
self.viewModel = viewModel
// ^ Cannot assign to property: 'viewModel' is a get-only property
}
...
}
To make it compile, you have to initialize the underlying _viewModel
stored property of type StateObject<ViewModel>
:
struct NameView: View {
@StateObject var viewModel: ViewModel
init(_ viewModel: ViewModel) {
_viewModel = .init(wrappedValue: viewModel)
}
...
}
But there's something hidden there. StateObject.init(wrappedValue:)
is declared like this:
public init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType)
So the expression given as an argument (just viewModel
above) is wrapped up in a closure, and is not evaluated right away. That closure is stored for later use, which is why it is @escaping
.
As you might guess from the hoops we have to jump through to make it compile, this is a weird way to use StateObject
. The normal use looks like this:
struct NormalView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
Text(viewModel.name)
}
}
And doing it the weird way has some drawbacks. To understand the drawbacks, we need to look at the context in which makeView()
or NormalView()
is evaluated. Let's say it looks like this:
struct ContentView: View {
@Binding var count: Int
var body: some View {
VStack {
Text("count: \(count)")
NormalView()
ViewFactory().makeView()
}
}
}
When count
's value changes, SwiftUI will ask ContentView
for its body
again, which will evaluate both NormalView()
and makeView()
again.
So body
calls NormalView()
during this second evaluation, which creates another instance of NormalView
. NormalView.init
creates a closure which calls ViewModel()
, and passes the closure to StateObject.init(wrappedValue:)
. But StateObject.init
does not evaluate this closure immediately. It stores it away for later use.
Then body
calls makeView()
, which does call ViewModel()
immediately. It passes the new ViewModel
to NameView.init
, which wraps the new ViewModel
in a closure and passes the closure to StateObject.init(wrappedValue:)
. This StateObject
also doesn't evaluate the closure immediately, but the new ViewModel
has been created regardless.
Some time after ContentView.body
returns, SwiftUI wants to call NormalView.body
. But before doing so, it has to make sure the StateObject
in this NormalView
has a ViewModel
. It notices that this NormalView
is replacing a prior NormalView
at the same position in the view hierarchy, so it retrieves the ViewModel
used by that prior NormalView
and puts it in the StateObject
of the new NormalView
. It does not execute the closure given to StateObject.init
, so it does not create a new ViewModel
.
Even later, SwiftUI wants to call NameView.body
. But before doing so, it has to make sure the StateObject
in this NameView
has a ViewModel
. It notices that this NameView
is replacing a prior NameView
at the same position in the view hierarchy, so it retrieves the ViewModel
used by that prior NameView
and puts it in the StateObject
of the new NameView
. It does not execute the closure given to StateObject.init
, and so it does not use the ViewModel
referenced by that closure. But the ViewModel
was created anyway.
So there are two drawbacks to the weird way in which you're using @StateObject
:
- You're creating a new
ViewModel
each time you callmakeView
, even though thatViewModel
may never be used. This may be expensive, depending on yourViewModel
. - You're creating the
ViewModel
while theContentView.body
getter is running. If creating theViewModel
has side effects, this may confuse SwiftUI. SwiftUI expects thebody
getter to be a pure function. In theNormalView
case, SwiftUI is calling theStateObject
's closure at a known time when it may be better prepared to handle side effects.
So, back to your original question:
Should it be
@StateObject
or@ObservedObject
?
Well, ha ha, that's difficult to answer without seeing an example that's less of a toy. But if you do need to use @StateObject
, you should probably try to initialize it in the ‘normal’ way.
What is the difference between @StateObject and @ObservedObject in child views in swiftUI
As you've written it, both @StateObject
and @ObservedObject
are doing the same thing in the child view. But, neither is correct because they are unnecessarily creating a new TestModel
just to toss it and replace it with the one being passed in.
The correct way to write the child view is:
@ObservedObject var model: TestModel
In this case, no initial value is assigned to model
in the child view, which means the caller will have to provide it. This is exactly what you want. One source of truth which is the model
in the parent view.
Also, state variables (both @State
and @StateObject
) should be private
to a view and should always be marked with private
. If you had done this:
@StateObject private var model = TestModel()
in the child view, then you wouldn't have been able to pass the model from the parent view and you would have seen that only @ObservedObject
can be used in this case.
Upon further testing, it seems that Swift/SwiftUI avoids creating the TestModel
in the child view when it is written as @ObservedObject var model = TestModel()
, but that syntax is still misleading to the reader and it should still be written as @ObservedObject var model: TestModel
because that makes it clear that model
is being initialized from somewhere else (that is, from the parent view).
What's the difference between @State and @StateObject
@State
is a variable that is meant to hold value types such as Bools, Integers, Strings, structs and so on. Apple simply doesn't intend for @State
to be used on reference types such as ObservableObjects
, because once again, State is meant to store value types such as Int, not instances of classes. Apple says in the documentation,
Don’t use state properties for persistent storage because the life cycle of state variables mirrors the view life cycle. Instead, use them to manage transient state that only affects the user interface, like the highlight state of a button, filter settings, or the currently selected list item.
In short, you could use @State
variables to store, say, the number of times a user has clicked a button (since the app started), but not to store another ObservableObject
. Also, if you look at this, the article shows that using an Object with State will not cause the view's actual variable to update. This is because, again to quote the article,
Because we’re using a complex, reference type, the value of state itself never changes. While a property of state , num has changed, the @State property wrapper has no idea because it is only watching the variable state, not any of its properties. To SwiftUI, because it is only watching state, it has no idea that num has changed, and so never re-renders the view.
An @StateObject
, on the other hand, can store things such as ObservableObjects
. When the value of the Object changes, it will cause a view update, because all of it is observed by SwiftUI
. This will only happen with @Published
properties, though, and any change of those properties will, once again, cause the view to re-render. An important note, too: @StateObject
will create a new instance every time the view appears. If you need to persist the values, you would need to pass in the object from a more root view. Also, an @StateObject
can be changed from outside the view, whereas @State
variables are only intended to be private and local. For more information on why this is, refer to the Apple State
documentation: https://developer.apple.com/documentation/swiftui/state
Resources:
- https://story.tomasen.org/swiftui-difference-of-state-binding-environment-and-environmentobject-and-when-to-use-them-ff80699f45b7
- https://www.hackingwithswift.com/quick-start/swiftui/what-is-the-stateobject-property-wrapper
- https://www.hackingwithswift.com/quick-start/swiftui/whats-the-difference-between-observedobject-state-and-environmentobject
- https://levelup.gitconnected.com/state-vs-stateobject-vs-observedobject-vs-environmentobject-in-swiftui-81e2913d63f9#:~:text=Use%20%40%20State%20for%20very%20simple,than%20what%20%40State%20can%20handle.
What is the difference between @EnvironmentObject and @ObservedObject?
As you've noticed an @ObservedObject
needs to be passed from view to view. It may be better for a simple view hierarchy when you don't have too many views.
Let's assume you have the following hierarchy:
ViewA -> ViewB -> ViewC -> ViewD
Now if you want your @ObservedObject
from the ViewA
to be in the ViewB
there's no problem with passing it directly in init
.
But what if you want it in the ViewD
as well? And what if you don't need it in the ViewB
and ViewC
?
With an @ObservedObject
you'd need to manually pass it from the ViewA
to the ViewB
and then to the ViewC
, and then to the ViewD
. And you'd need to declare it in every child view.
With an @EnvironmentObject
it's easy - just pass it to the top-level view:
ViewA().environmentObject(someObservableObject)
Then you only declare it in the view that uses it - this may make your code more readable.
Note
Every object in the environment (view hierarchy) can access the injected @EnvironmentObject
. If you don't want this (privacy is important) you may need to pass it as an @ObservedObject
instead.
What is the difference between @State and @ObservedObject, can they both be used to persist state?
@ObservedObject
does not persist state
Can I use
@ObservedObject
to create persisted state?
On its own, you cannot. The Apple documentation has this to say about @State
:
A persistent value of a given type, through which a view reads and monitors the value.
But I found no mention of persistence with @ObservedObject
so I constructed this little demo which confirms that @ObservedObject
does not persist state:
class Bar: ObservableObject {
@Published var value: Int
init(bar: Int) {
self.value = bar
}
}
struct ChildView: View {
let value: Int
@ObservedObject var bar: Bar = Bar(bar: 0)
var body: some View {
VStack(alignment: .trailing) {
Text("param value: \(value)")
Text("@ObservedObject bar: \(bar.value)")
Button("(child) bar.value++") {
self.bar.value += 1
}
}
}
}
struct ContentView: View {
@State var value = 0
var body: some View {
VStack {
Spacer()
Button("(parent) value++") {
self.value += 1
}
ChildView(value: value)
Spacer()
}
}
}
Whenever you click on the value++
button, it results in a re-render of ChildView
because the value
property changed. When a view is re-rendered as a result of a property change, it's @ObservedObject
s are reset
In contrast, if you add a @State
variable to the ChildView
you'll notice that it's value is not reset when the @ObservedObject
is reset.
Using persisted state with @ObservedObject
To persist state with @ObservedObject
, instantiate the concrete ObservableObject
with @State
in the parent view. So to fix the previous example, would go like this:
struct ChildView: View {
let value: Int
@ObservedObject var bar: Bar // <-- passed in by parent view
var body: some View {
VStack(alignment: .trailing) {
Text("param value: \(value)")
Text("@ObservedObject bar: \(bar.value)")
Button("(child) bar.value++") {
self.bar.value += 1
}
}
}
}
struct ContentView: View {
@State var value = 0
@State var bar = Bar(bar: 0) // <-- The ObservableObject
var body: some View {
VStack {
Spacer()
Button("(parent) value++") {
self.value += 1
}
ChildView(value: value, bar: bar).id(1)
Spacer()
}
}
}
The definition of the class Bar
is unchanged from the first code example. And now we see that the value is not reset even when the value
property changes:
How do I share/bind @StateObject between SwiftUI views?
Inject it as environment object, like
@StateObject var state = State()
var body: some View {
VStack(spacing: Constants.spacing) {
// ... other code
}
.background(...)
.clipShape(...)
.environmentObject(state) // << here !!
and use it inside children:
struct PaletteBar: View {
// MARK: - Properties
@EnvironmentObject var state: State // << injected by parent !!
// ...
}
@State vs @ObservableObject - which and when?
If you mark any variables as @State
in a SwiftUI View
and bind them to a property inside the body
of that View
, the body
will be recalculated whenever the @State
variable changes and hence your whole View
will be redrawn. Also, @State
variables should serve as the single source of truth for a View
. For these reasons, @State
variables should only be accessed and updated from within the body
of a View
and hence should be declared private
.
You should use @State
when you are binding some user input (such as the value of a TextField
or the chosen value from a Picker
). @State
should be used for value types (struct
s and enum
s).
On the other hand, @ObservedObject
should be used for reference types (class
es), since they trigger refreshing a view whenever any @Published
property of the ObservableObject
changes.
You should use @ObservedObject
when you have some data coming in from outside your View
, such as in an MVVM architecture with SwiftUI, your ViewModel
should be stored as an @ObservedObject
on your View
.
A common mistake with @ObservedObject
s is to declare and initialise them inside the View
itself. This will lead to problems, since every time the @ObservedObject
emits an update (one of its @Published
properties gets updated), the view will be recreated - which will also create a new @ObservedObject
, since it was initialised in the View
itself. To avoid this problem, whenever you use @ObservedObject
, you always have to inject it into the view. The iOS 14 @StateObject
solves this issue.
Related Topics
Iphone Get Ssid Without Private Library
How to Detect When Someone Shakes an Iphone
Difference Between 'Yyyy' and 'Yyyy' in Nsdateformatter
How to Set the Cookies to Be Used by a Wkwebview
Number of Days Between Two Nsdates
Libc++Abi.Dylib: Terminating With Uncaught Exception of Type Nsexception (Lldb)
Can't Use Swift Classes Inside Objective-C
Ios 9 Xcode 7 - Application Appears With Black Bars on Top and Bottom
Ios Upload Image and Text Using Http Post
Fatal Error: Unexpectedly Found Nil While Unwrapping an Optional Values
Objective-C Arc: Strong VS Retain and Weak VS Assign
Uiscrollview Scrollable Content Size Ambiguity
Status Bar and Navigation Bar Issue in Ios7
Iphone Hide Navigation Bar Only on First Page
How to Record a Conversation/Phone Call on Ios
Uiimageview - How to Get the File Name of the Image Assigned