Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between ObservedObject and StateObject in SwiftUI

Tags:

ios

swift

swiftui

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?

like image 291
matteopuc Avatar asked Jun 23 '20 21:06

matteopuc


People also ask

What is the difference between StateObject and ObservedObject?

ObservedObjects are usually used when the data is stored outside of the view so when the view is redrawn, we won't lose the data. StateObjects are usually used when that data needs to be stored inside of the view. Many people are confused by these two property wrappers.

What is ObservedObject SwiftUI?

A property wrapper type that subscribes to an observable object and invalidates a view whenever the observable object changes.

What is a StateObject SwiftUI?

@StateObject var model = DataModel() SwiftUI creates a new instance of the object only once for each instance of the structure that declares the object. When published properties of the observable object change, SwiftUI updates the parts of any view that depend on those properties: Text(model.

What is EnvironmentObject SwiftUI?

An @EnvironmentObject is an object living in the current environment, available to read whenever needed. An environment object is defined at a higher-level view, and can any child view can access it if needed.

When should I use @stateobject vs @observedobject?

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.

What is @stateobject in SwiftUI?

@StateObject is a new property wrapper that initializes an instance of a class conforming to the protocol ObservableObject, and stores it in the internal memory of the SwiftUI framework. SwiftUI only creates one @ StateObject for each container that declares it and stores it outside of the view’s lifecycle.

What is observableobject in SwiftUI?

Both property wrappers require your object to conform to the ObservableObject protocol. This protocol stands for an object with a publisher that emits before the object has changed and allows you to tell SwiftUI to trigger a view redraw.

What is a stateobject in a view?

In other words, data of a StateObject is stored in the view, so the view won’t lose the data during redrawing. In this example, UserView does one simple thing, it adds "+” to the name every time Text is tapped, and userViewModel is declared with StateObject. Let’s add the UserView to another view. This view has a State property of count.


Video Answer


4 Answers

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

  • What’s the difference between @StateObject and @ObservedObject?
  • What’s the difference between @ObservedObject, @State, and @EnvironmentObject?
  • What is the @StateObject property wrapper?
like image 62
pawello2222 Avatar answered Oct 23 '22 19:10

pawello2222


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
}
like image 30
samwize Avatar answered Oct 23 '22 19:10

samwize


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
}
like image 17
Dávid Pásztor Avatar answered Oct 23 '22 19:10

Dávid Pásztor


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()

        }
    }
}

like image 7
LiangWang Avatar answered Oct 23 '22 17:10

LiangWang