What is the difference between ObservedObject and StateObject in SwiftUI

IosSwiftSwiftui

Ios Problem Overview


If I have an ObservableObject in SwiftUI I can refer to it as an @ObservedObject:

class ViewModel: ObservableObject {
    @Published var someText = "Hello World!"
}

struct ContentView: View {
    @ObservedObject var viewModel = ViewModel()
    
    var body: some View {
        Text(viewModel.someText)
    }
}

Or as a @StateObject:

class ViewModel: ObservableObject {
    @Published var someText = "Hello World!"
}

struct ContentView: View {
    @StateObject var viewModel = ViewModel()

    var body: some View {
        Text(viewModel.someText)
    }
}

But what's the actual difference between the two? Are there any situations where one is better than the other, or they are two completely different things?

Ios Solutions


Solution 1 - Ios

@ObservedObject

When a view creates its own @ObservedObject instance it is recreated every time a view is discarded and redrawn:

struct ContentView: View {
  @ObservedObject var viewModel = ViewModel()
}

On the contrary a @State variable will keep its value when a view is redrawn.

@StateObject

A @StateObject is a combination of @ObservedObject and @State - the instance of the ViewModel will be kept and reused even after a view is discarded and redrawn:

struct ContentView: View {
  @StateObject var viewModel = ViewModel()
}

Performance

Although an @ObservedObject can impact the performance if the View is forced to recreate a heavy-weight object often, it should not matter much when the @ObservedObject is not complex.

When to use @ObservedObject

It might appear there is no reason now to use an @ObservedObject, so when should it be used?

> You should use @StateObject for any observable properties that you > initialize in the view that uses it. If the ObservableObject instance > is created externally and passed to the view that uses it mark your > property with @ObservedObject.

Note there are too many use-cases possible and sometimes it may be desired to recreate an observable property in your View. In that case it's better to use an @ObservedObject.

Useful links:

Solution 2 - Ios

Apple documentation did explain why initializing with ObservedObject is unsafe.

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

The solution is StateObject.

At the same time, the documentation showed us how we should create data models in a view (or app/scene) when it can hold on to the truth, and pass it to another view.

struct LibraryView: View {
    @StateObject var book = Book() // Hold on to the 1 truth
    var body: some View {
        BookView(book: book) // Pass it to another view
    }
}

struct BookView: View {
    @ObservedObject var book: Book // From external source
}

Solution 3 - Ios

Even though pawello2222's answer have nicely explained the differences when the view itself creates its view model, it's important to note the differences when the view model is injected into the view.

When you inject the view model into the view, as long as the view model is a reference type, there are no differences between @ObservedObject and @StateObject, since the object that injected the view model into your view should hold a reference to view model as well, hence the view model isn't destroyed when the child view is redrawn.

class ViewModel: ObservableObject {}

struct ParentView: View {
    @ObservedObject var viewModel = ViewModel()

    var body: some View {
        ChildView(viewModel: viewModel) // You inject the view model into the child view
    }
}

// Even if `ChildView` is discarded/redrawn, `ViewModel` is kept in memory, since `ParentView` still holds a reference to it - `ViewModel` is only released and hence destroyed when `ParentView` is destroyed/redrawn.
struct ChildView: View {
    @ObservedObject var viewModel: ViewModel
}

Solution 4 - Ios

Here is an example to illustrate the difference.

Every time you click the Refresh button the StateObjectClass is recreated from scratch only for CountViewObserved. This means it's @Published count property gets the default value of 0 when this happens.

The difference between @StateObject and @ObservedObject is clear. The @StateObject version of the observed StateObjectClass preserves its state since it is never deinitted. The @ObservedObject version does not as it is recreated. So you should use @StateObject for the owner of an ObservableObject.

import SwiftUI

class StateObjectClass: ObservableObject {
    enum ObserverType: String {
        case stateObject
        case observedObject
    }
    
    @Published var count = 0
    let type: ObserverType
    let id = UUID()
    init(type: ObserverType) {
        self.type = type
    }
    deinit {
        print(#function, "type: \(type.rawValue) id: \(id)")
    }
}

struct CountViewState: View {
    @StateObject var state = StateObjectClass(type: .stateObject)
    var body: some View {
        VStack {
            Text("@StateObject's count: \(state.count)")
            Button("ADD 1"){
                state.count += 1
            }
        }
    }
}

struct CountViewObserved: View {
    @ObservedObject var state = StateObjectClass(type: .observedObject)
    var body: some View {
        VStack {
            Text("@ObservedObject's count: \(state.count)")
            Button("Add 1") {
                state.count += 1
            }
        }
    }
}

struct ContentView: View {
    @State private var count = 0
    var body: some View {
        VStack {

            Text("Refresh CounterView's count: \(count)")
            
            Button("Refresh") {
                count += 1
            }

            CountViewState()
                .padding()

            CountViewObserved()
                .padding()

        }
    }
}

Solution 5 - Ios

@StateObject is a state of a given view, thus the instance of it is retained by SwiftUI across body updates. It is not retained though when running in Preview.

@ObservedObject on the other hand is just an object being observed by given View, thus is not retained by SwiftUI (it has to be retained outside of the View).

In other words - it looks like SwiftUI keeps a strong reference of @StateObject and unowned reference of @ObservedObject.

Retained vs non-retained source, Previews behavior source, around ~8:30.

Solution 6 - Ios

The difference between let's say :

@ObservedObject var book: BookModel

And

@StateObject var book: BookModel

@ObservedObject does NOT own the instance book, its your responsibility to manage the life cycle of the instance..

But when you want to tie the life cycle of your observable object book to your view like in @State you can use @StateObject.

In this case SwiftUI will OWN the observable object and the creation and destruction will be tied to the view's life cycle

SwiftUI will keep the object alive for the whole life cycle of the view

This is great for expensive resources, you do not need to fiddle with onDisappear anymore to release resources.

This clarification is taken from WWDC2020 Data essentials in SwiftUI

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmatteopucView Question on Stackoverflow
Solution 1 - Iospawello2222View Answer on Stackoverflow
Solution 2 - IossamwizeView Answer on Stackoverflow
Solution 3 - IosDávid PásztorView Answer on Stackoverflow
Solution 4 - IosLiangWangView Answer on Stackoverflow
Solution 5 - IosMaciek CzarnikView Answer on Stackoverflow
Solution 6 - IosRasheedView Answer on Stackoverflow