In short - when construction a custom SwiftUI View with a ViewModel ("MyView"/"MyViewModel") like this:
struct ContentView: View {
var body: some View {
MyView(viewModel: MyViewModel())
}
}
Why is this:
struct MyView: View {
@StateObject var viewModel: MyViewModel
var body: some View {
Text("Hello world!")
}
}
Not the same as this:
struct MyView: View {
@StateObject var viewModel: MyViewModel
init(viewModel: MyViewModel) {
self._viewModel = StateObject(wrappedValue: viewModel)
}
var body: some View {
Text("Hello world!")
}
}
When debugging, it looks like the second option causes new instances of MyViewModel
to be created every time MyView
is reconstructed by SwiftUI. But in the first option, the StateObject wrapper seems to do its job, and makes sure only one instance of MyViewModel
is created for all reconstructions of MyView
.
Is there some extra SwiftUI magic applied to using the View's default memberwise initializer VS a custom one? Perhaps by the ViewBuilder?
Attachment: A simple example app, MyApp.swift, to see the behaviour in action.
I also cross posted this question on StackOverflow, and got a super nice answer back 🙌 Turns out I was missing a detail in the documentation of StateObject. The initializer to StateObject is prefixed with @autoclosure, and so the instantiation of MyViewModel is wrapped in a closure lazily evaluated by SwiftUI (inside StateObject initializer). That way MyViewModel is only created once!
In short: by writing the custom initializer like below, we are good and get the expected behaviour 🥳
struct MyView: View {
@StateObject var viewModel: MyViewModel
init(viewModel: @autoclosure @escaping () -> MyViewModel) {
self._viewModel = StateObject(wrappedValue: viewModel())
}
var body: some View {
Text("Hello world!")
}
}