9

This is from Apple documentation on the SwiftUI App protocol:

@main
struct Mail: App {
    @StateObject private var model = MailModel()

    var body: some Scene {
        WindowGroup {
            MailViewer()
                .environmentObject(model) // Passed through the environment.
        }
        Settings {
            SettingsView(model: model) // Passed as an observed object.
        }
    }
}

Why do we need to use the @StateObject propertyWrapper in this case? Why isn't a normal property enough?

I suspect that the "App" struct is a configuration object, just like views in SwiftUI? And that we can't count on that struct to hang around once the body has been read? Correct?

2 Answers 2

5

The main reason is for previews, models that are initialised with @StateObject are not initialised. WWDC 2020 Structure your app for SwiftUI previews 8:19

Secondly, the model changes, @StateObject will allow SwiftUI to detect the change and cause the body to be re-computed because model is referenced (twice) within the body, which SwiftUI knows by dependency tracking. This means that MailViewer and SettingsView will be re-created with the new data in the model. Then if anything changed in those View structs, SwiftUI will detect that by diffing the new structs from the ones returned previously and update the screen with whatever changes are needed to bring the screen up to date.

As you say, we can't guarantee structs to hang around, in fact they do not, they are created, the screen is rendered and they are thrown away. That is why we use property wrappers so when the struct is created again it is given the same data to use for the property. In the case of @StateObject the object is created once, the first time the body is computed of the first struct. If a struct is no longer being created, e.g. it is excluded by an if statement, then the object is deinit. If the struct is created again in the future then a new object is created, this is more of a feature for Views than for Apps. This means that the state object's life cycle is tied to the life cycle of the View being shown on screen which is very powerful.

If we were to use normal properties to init objects on SwiftUI structs then those objects would be created every time a struct is re-created which is a heap allocation which fills up RAM and slows down SwiftUI and should be avoided at all costs.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks @malhal for your answer. StateObject makes sense to me when used deeper in the view hierarchy, because I expect those views to be recreated many times. But I'm not sure I fully understand the use in the root App struct. Will this root struct be recreated many times as well?
I think it's better to assume the AppStruct will be recreated even if right now it is difficult to find the situation when it might happen. It's the same as in SwiftUI v1.0 we didn't think the root ContentView would ever be recreated but now we have App and Scene now it is. The 2 main reasons for using @StateObject in the App struct is the object is init just before the body is run rather than when the struct is init (which allows SwiftUI to store the object in the correct place), and that when the object is changed the App body is recomputed.
3

According to Apple @StateObject guaranties that model in

@StateObject private var model = MailModel()

will be created only once. That's all difference from @ObservedObject. So if that is not important for you (or not the case) and you don't need to observe it at that level then you can use regular property declaration.

1 Comment

How would something at the app level be created more than once?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.