54
votes

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?

6
This link actually explains it much better than all of the answers here: hackingwithswift.com/quick-start/swiftui/… - Big_Chair

6 Answers

71
votes

@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:

22
votes

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
}
15
votes

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
}
5
votes

Here is an example. Every time you click refresh button, CountViewObserved force StateObjectClass to be destroyed/recreated, so you can see 0, which is not expected.

import SwiftUI
import Combine

class StateObjectClass:ObservableObject{
    let type:String
    let id:Int
    @Published var count = 0
    init(type:String){
        self.type = type
        self.id = Int.random(in: 0...1000)
        print("type:\(type) id:\(id) init")
    }
    deinit {
        print("type:\(type) id:\(id) deinit")
    }
}

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

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

struct ContentView: View {
    @State var count = 0
    var body: some View {
        VStack{
            Text("refresh CounterView count :\(count)")
            Button("refresh"){
                count += 1
            }

            CountViewState()
                .padding()

            CountViewObserved()
                .padding()

        }
    }
}

1
votes

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

1
votes

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 observableObject "book" to your view like in @State you can use @StateObject. In this case SwiftUI will OWN the observableObject 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 :