A first look at SwiftUI’s new StateObject property wrapper
Discover page available: SwiftUIBeing a declarative UI framework, state and data management is an incredibly important part of SwiftUI. Since day one, it has been shipping with a suite of protocols and property wrappers that let us define, update and observe our view’s data in various ways — and this year, there’s a new member of that family: StateObject
.
Let’s say that we’re working on an app for browsing movies, and that we’re storing those movies using a MovieStore
class — which is an ObservableObject
that exposes an array of Movie
values using the Published
property wrapper:
class MovieStore: ObservableObject {
@Published private(set) var movies: [Movie]
...
}
Before the version of SwiftUI included in Xcode 12, we might’ve then used the ObservedObject
property wrapper to enable a MovieListView
to observe an injected instance of the above store class — like this:
struct MovieListView: View {
@ObservedObject var store: MovieStore
var body: some View {
NavigationView {
List(store.movies) { movie in
NavigationLink(movie.name,
destination: MovieDetailsView(
store: store,
movieID: movie.id
)
)
}
.navigationTitle("My movies")
}
}
}
Although the above MovieListView
does hold a reference to the MovieStore
that it was injected with, and can pass that store instance along to any child views that it creates (such as the above MovieDetailsView
), our view is actually not the owner of that store object.
It’s important to remember that SwiftUI views are not view objects in the “classic sense”, but rather lightweight value descriptions of our UI, which means that our view instances don’t really have lifecycles like class instances do. Therefore, we always need to make sure to retain any ObservableObject
instances that we inject into our views elsewhere — for example within some form of dependency container.
This is where StateObject
comes in, which provides a built-in way to have one of our views assume ownership over an ObservableObject
. That way, we no longer need to retain that object elsewhere, since SwiftUI will manage it for us automatically, even as our views get updated and their values recreated.
Something that’s really nice is that if we wanted to change our above MovieListView
to now use StateObject
instead, we can actually keep the whole body
of our view exactly the same — all that we need to do is replace ObservedObject
with StateObject
, and give our store
property a default value — like this:
struct MovieListView: View {
@StateObject private var store = MovieStore()
var body: some View {
...
}
}
Now, this doesn’t mean that StateObject
is always a better alternative compared to ObservedObject
— both have pros and cons. We should still keep using ObservedObject
for injected dependencies that are retained and managed outside of our view hierarchy, while StateObject
can be a great alternative for reference types that are used to keep track of a view’s internal state.