Articles, podcasts and news about Swift development, by John Sundell.

A first look at SwiftUI’s new StateObject property wrapper

Published on 25 Jun 2020
Discover page available: SwiftUI

Being 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.