Mekal Z

Mekal Z

A programmer running out of the wall.
twitter

SwiftUI: Differences between @ObservedObject and @StateObject

Basics#

In SwiftUI,@ObservedObject and @StateObject are both property wrappers used to manage object state in views.

@ObservedObject is used to marks objects of ObservableObject protocol as objects observed by the current view. That means while the state of objects marked by @ObservableObject changes, the view automatically refreshes. Typically, @ObservedObject is used to share state between views.

For example, suppose there is a UserSettings class that complies with ObservableObject protocol and used to store user preferences.

class UserSettings: ObservableObject {
    @Published var theme: Theme = .light
}

We can watch UserSetting object with @ObservedObject property wrapper in the views as follows:

struct SettingsView: View {
    @ObservedObject var userSettings = UserSettings()

    var body: some View {
        // ...
    }
}

In this example, when user changes preferences, @Published property of UserSettings will be updated and the SettingsView will be automatically refreshed.

On the other hand, @StateObject marks an object that complies ObservableObject protocol as a state object owned by the current view. This means that when an object marked by @StateObject changes, it only affects the view that own the object. Typically, @StateObject is used to manage state in a single view.

For example, suppose there is a TimerModel class that complies ObservableObject and used to manage the state of the Timer.

class TimerModel: ObservableObject {
    @Published var time: Double = 0

    init() {
        Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            self.time += 1
        }
    }
}

We can create a state variable that owns TimerModel with @StateObject as follows.

struct TimerView: View {
    @StateObject var timerModel = TimerModel()

    var body: some View {
        Text("\(timerModel.time)")
    }
}

In this example, only TimerView will be affected when there is a change of @Published property of object TimerModel. Other views won't receive any updates.

The true difference#

There is no quite clear differences in the former examples. So why does the object wrapped with @StateObject only affects the current view?

There is indeed not so much differences in the example above. But when it came to a situation that the object of @ObservableObject is passed into the two views by it parent view, there will be a significant difference.

When we create a instance of an object with @StateObject in a view, the instance will he a same life cycle with the view, which means it will be destroyed while the view be destroyed.

So objects with wrapper of @StateObject will only exists in the current view and will only affect the current view as well. If other views need the same object, an @ObservedObject wrapper should be used.

In the example above, we created a TimerModel instance with @StateObject wrapper and store it in a property of timerModel. So the object timerModel only exists in TimerView and cannot be reached by other views.

Below is a example that an object of ObservableObject is watched by multiple views.

class UserSettings: ObservableObject {
    @Published var theme: Theme = .light
}
struct SettingsView: View {
    @ObservedObject var userSettings: UserSettings

    var body: some View {
        VStack {
            Text("Settings View")
            Text("Current Theme: \(userSettings.theme.rawValue)")
        }
    }
}

struct ProfileView: View {
    @ObservedObject var userSettings: UserSettings

    var body: some View {
        VStack {
            Text("Profile View")
            Text("Current Theme: \(userSettings.theme.rawValue)")
        }
    }
}
struct ContentView: View {
    @StateObject var userSettings = UserSettings()

    var body: some View {
        VStack {
            SettingsView(userSettings: userSettings)
            ProfileView(userSettings: userSettings)
        }
    }
}

In this example, when the user updates theme property of userSettings, SettingsView and ProfileView will automatically update and display the latest theme. This is the basic implement of multiple views watching a same object of ObservableObject protocol.

If we use @StateObject in either SettingsView or ProfileView, it would be like we pass argument into the two views and they each make a copy in their own context. In this kind of situation, the state change will be constrained in seperate context and won't effect each other.

Share an image as the post cover
This is a avatar I generated with Midjourney yesterday for Jaina. She liked it very much.

linguatale_appicon.png

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.