Difference between @StateObject, @EnvironmentObject and @ObservedObject in SwiftUI

Translation of the article was prepared on the eve of the start of the course “iOS Developer. Professional”


I decided to devote this week to data streams in SwiftUI. In this article, we will discuss the difference between property wrappers (property wrappers) @StateObject, @EnvironmentObjectand @ObservedObjectas I know this is the most confusing topic for newbies to SwiftUI.

Why do we need property wrappers in SwiftUI?

SwiftUI uses immutable struct types to describe a hierarchy of views. All views that SwiftUI provides are immutable. This is why SwiftUI gives us a set of property wrappers to handle data changes. Property wrappers allow us to declare ourselves inside SwiftUI views, but store data outside of the view that declares the wrapper.

@StateObject

@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. Let’s take a look at some examples where we use @StateObject to save the state of the whole application.

import SwiftUI
 
@main
struct CardioBotApp: App {
    @StateObject var store = Store(
        initialState: AppState(),
        reducer: appReducer,
        environment: AppEnvironment(service: HealthService())
    )
 
    var body: some Scene {
        WindowGroup {
            RootView().environmentObject(store)
        }
    }
}

As you can see @StateObject ideal for storing the state of an entire application and passing it on to various scenes or views within your application. SwiftUI will store it in the framework’s special memory so that your data is in a safe place outside of the scene or view lifecycle.

If you want to know more about the implementation of the Single State Container concept, read my article “Redux-like state container in SwiftUI. Basics “

@ObservedObject

@ObservedObject this is another way to subscribe and follow the changes in ObservableObjectSwiftUI has no lifecycle control @ObservedObject – you must take care of this yourself. @ObservedObject perfect for the occasion when you have ObservableObject, stored in @StateObjectand you need to share it with some reusable view.

I mentioned reusable views because I myself use CalendarContainerView in multiple places in my application and I don’t want it to depend on external conditions. I use @ObservedObject, to explicitly indicate the data used by the view in this particular case.

NavigationLink(
    destination: CalendarContainerView(
        store: transformedStore,
        interval: .twelveMonthsAgo
    )
) {
    Text("Calendar")
}

If you want to know more about using Container View, read my article “Redux-like state container in SwiftUI. Container Views “

@EnvironmentObject

@EnvironmentObject is a great way to implicitly inject an instance of a class that matches ObservableObject, into part of the presentation hierarchy. Let’s say you have a module in your application that contains 3-4 screens and they all use the same ViewModel. If you don’t want to explicitly pass the same ViewModel from one view to another, then you will need @EnvironmentObject… Let’s see how we can use it.

@main
struct CardioBotApp: App {
    @StateObject var store = Store(
        initialState: AppState(),
        reducer: appReducer,
        environment: .production
    )
 
    var body: some Scene {
        WindowGroup {
            TabView {
                NavigationView {
                    SummaryContainerView()
                        .navigationBarTitle("today")
                        .environmentObject(
                            store.derived(
                                deriveState: .summary,
                                embedAction: AppAction.summary
                            )
                        )
                }
 
                NavigationView {
                    TrendsContainerView()
                        .navigationBarTitle("trends")
                        .environmentObject(
                            store.derived(
                                deriveState: .trends,
                                embedAction: AppAction.trends
                            )
                        )
                }
            }
        }
    }
}

In the above example, we are injecting environmentObject into the view hierarchy SummaryContainerView… SwiftUI will implicitly grant embedded environmentObjects access to all child views that are inside SummaryContainerView… We can quickly get and subscribe to embedded environmentObjects using the property wrapper @EnvironmentObject

struct SummaryContainerView: View {
    @EnvironmentObject var store: Store<SummaryState, SummaryAction>
 
    var body: some View {
        //......

I must mention that @EnvironmentObject has the same life cycle as @ObservedObject… This means that you can get a new environmentObject whenever you create it inside a view, which can be recreated using SwiftUI.

If you want to know more about advanced techniques for using a Single State Container, read my article “Redux-like state container in SwiftUI. Best practics”

Conclusion

Today we talked about the differences between property wrappers @StateObject, @EnvironmentObjectand @ObservedObject… Hopefully this article helps you understand which property wrapper is best for your case. Feel free to follow me at Twitter and ask your questions related to this article. Thank you for your attention, see you next week!

Learn more about the course.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *