Weekly Swift articles, podcasts and tips by John Sundell.

A first look at SwiftUI’s new StateObject property wrapper

Published on 25 Jun 2020
Basics article 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.

⚠️ This article is covering technologies that are currently in beta as part of Xcode 12, so it’s possible that some of the APIs used will change during the beta period.

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.

Support Swift by Sundell by checking out this sponsor:

Instabug
Instabug

Instabug: Investigate, diagnose and resolve issues up to four times faster. Whether it’s a crash, slow screen transitions, slow network calls or unresponsive UIs, Instabug lets you utilize powerful performance patterns to trace the cause of each issue. Detect if a specific app version, device or network connection is affecting the user experience and spot trends and spikes. Get started now and ship apps that your users will love.