I've been watching Apple's concurrency talks from WWDC21 as well as reading a ton of articles on Apple's concurrency updates; however, I cannot wrap my head around one thing: Why do people provide guidance that you should annotate view models with @MainActor
? From what I have read, by making a view model property a @StateObject
or @ObservedObject
within a view, it automatically becomes @MainActor
. So if that's the case, why do people still recommend annotating view models as @MainActor
?
For context, here are a few of the articles I've read that reference this:
- https://www.hackingwithswift.com/quick-start/concurrency/understanding-how-global-actor-inference-works
- https://www.hackingwithswift.com/books/concurrency/how-to-use-mainactor-to-run-code-on-the-main-queue
- https://peterfriese.dev/swiftui-concurrency-essentials-part1/
Excerpt from the first link:
[A]ny struct or class using a property wrapper with @MainActor for its wrapped value will automatically be @MainActor. This is what makes @StateObject and @ObservedObject convey main-actor-ness on SwiftUI views that use them – if you use either of those two property wrappers in a SwiftUI view, the whole view becomes @MainActor too.
Excerpt from the second link:
[W]henever you use @StateObject or @ObservedObject inside a view, Swift will ensure that the whole view runs on the main actor so that you can’t accidentally try to publish UI updates in a dangerous way. Even better, no matter what property wrappers you use, the body property of your SwiftUI views is always run on the main actor.
Does that mean you don’t need to explicitly add @MainActor to observable objects? Well, no – there are still benefits to using @MainActor with these classes, not least if they are using async/await to do their own asynchronous work such as downloading data from a server.
All in all, I'm a bit confused by the guidance if it would be handled for us automatically. Especially since I don't know of a scenario where you'd have a view model in SwiftUI that is not an @ObservableObject
.
My last question, related to the first question, is: If @StateObject
and @ObservedObject
automatically make the view @MainActor
, then does @EnvironmentObject
also make a view @MainActor
?
To put some code behind this, I intend to have the following class injected into the environment using .environmentObject(...)
@MainActor
class UserSettings: ObservableObject {
@Published var flowUser: FlowUser?
init(flowUser: FlowUser? = nil) {
self.flowUser = flowUser
}
}
And the following is a view model for one of my views:
@MainActor
class CatalogViewModel: ObservableObject {
@Published var flowUser: FlowUser?
init(flowUser: FlowUser?) {
self.flowUser = flowUser
}
}
As you can see, I have made both classes @ObservableObjects
so I feel like I should be able to remove the @MainActor
annotation.
Any help would be greatly appreciated! Thanks for your time!
@MainActor
attribute, why don't you try removing it and see what happens? – Landtag@StateObject
would be misusing what it is designed for. – WhiskerObservable
addition in 2023 made them easier than ever, and it's a great way to separate out logic from the view. Apple speakers still explicitly mention them, and use them in several other WWDCs where they aren't mentioned by name. Google query to see them referenced explicitly:site:developer.apple.com/videos/play "view model"
– Barmaid.task
which gives the correct lifetime reference semantics so no need for an object. – Whisker