7

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:

  1. https://www.hackingwithswift.com/quick-start/concurrency/understanding-how-global-actor-inference-works
  2. https://www.hackingwithswift.com/books/concurrency/how-to-use-mainactor-to-run-code-on-the-main-queue
  3. 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!

Nirvan Nagar
  • 189
  • 3
  • 11
  • 2
    They (published) update UI (in majority of cases) and UI *must(!)* be updated only on main queue. – Asperi Dec 14 '21 at 20:06
  • If you want to know whether you can remove the `@MainActor` attribute, why don't you try removing it and see what happens? – matt Dec 14 '21 at 20:18
  • We don't use view model objects in SwiftUI (that is what the View struct is for) and to make one via`@StateObject` would be misusing what it is designed for. – malhal Apr 04 '23 at 14:26
  • @malhal "We don't use view model objects in SwiftUI" not true, if you don't use ViewModels doesn't mean no one is using them. Most of the SwiftUI apps I've seen are MVVM... – kironet May 21 '23 at 01:44
  • @kironet you’ll see less and less as they take time to learn the View struct – malhal May 21 '23 at 07:30

1 Answers1

5

An @ObservedObject or the others does not make it a main actor. So your statement is not true

see https://developer.apple.com/videos/play/wwdc2021/10019/ around minute 22 enter image description here

From what I have read, by making a view model property a @StateObject or @ObservedObject within a view, it automatically becomes @MainActor.

doozMen
  • 690
  • 1
  • 7
  • 14
  • In fact, property wrappers can influence actor isolation *today*. But, that is changing, thankfully, because it has caused so much confusion. https://github.com/apple/swift-evolution/blob/main/proposals/0401-remove-property-wrapper-isolation.md – Mattie Aug 01 '23 at 15:13