iosswiftswiftui

Why use @ObservableObject when you might as well use @StateObject?


I've been learning about swiftUI and @StateObject and @ObservedObject and thought I grasped it until I realized I could exchange @ObservedObject with @StateObject without any difference to the code behaviour. Consider this example:


class SharedModel: ObservableObject {
    @Published var value: String = "Initial Value"
}

struct ContentView: View {
    @StateObject var sharedModel = SharedModel()

    var body: some View {
        VStack {
            Text("Value in ContentView: \(sharedModel.value)")
            TextField("ContentView textfield", text: $sharedModel.value)

            SubView(sharedModel: sharedModel)
        }
    }
}

struct SubView: View {
    @ObservedObject var sharedModel: SharedModel

    var body: some View {
        VStack {
            Text("Value in SubView: \(sharedModel.value)")
            
            TextField("sub textfield", text: $sharedModel.value)
        }
    }
}

Writing something in either TextField will update the text in both Text views. So, ContentView and SubView will update when the property of SharedModel is updated. Great. But then I played around a bit and changed @ObservedObject to @StateObject and didn't notice any difference. It worked as before. The way I though it would work is that if a property is annotated with @StateObject then it's the source of truth and won't relay changes to other @StateObject properties.

So what's the deal here? What's the point of @ObservedObject and why should I use it if @StateObject works equally fine?


Solution

  • StateObject’s main difference is that it has storage within SwiftUI it can preserve an object when the View is reloaded.

    You might not notice any differences but using ObservedObject to initialize will cause leaks, inconsistent behavior and unnecessary loss of data.

    SwiftUI might create or recreate a view at any time, so it’s important that initializing a view with a given set of inputs always results in the same view. As a result, it’s unsafe to create an observed object inside a view. Instead, SwiftUI provides the StateObject property wrapper, which creates a single source of truth for a reference type that you store in a view hierarchy.

    https://developer.apple.com/documentation/swiftui/monitoring-model-data-changes-in-your-app

    When going from StateObject to StateObject you don’t get a new object because it is a class is a reference type and they would both be pointing to the same address. You would need a “deep” copy for them to be completely different.

    You would have 2 different o nexts trying to own the same thing within SwiftUI so you might end up with leaks.

    The new Observable can do what you describe if you pass an Observable to a State SwiftUI now creates a deep copy for you.