1

Why does the below code not create a compiler error?

I am mutating a @Published property of this ObservableObject on a background thread. Shouldn't the @MainActor tag on this class mean that any code which mutates a published property must occur on the main thread?

import SwiftUI

@MainActor
class ViewModel: ObservableObject {
        
    @Published private(set) var questionnaire: Questionnaire!
    
    init() {
        fetchQuestionnaire(id: "forest")
    }
    
    func fetchQuestionnaire(id: String) {
        Task {
            questionnaire = Questionnaire.testData
        }
    }
    
}

Or, is it actually OK to mutate @Published properties on a background thread, and SwiftUI will perform the didSet publisher on the main thread?

And if that’s the case, what even really is the point of @MainActor in this situation?

Rob
  • 415,655
  • 72
  • 787
  • 1,044
Plato
  • 111
  • 2
  • 7
  • 3
    It's not a bug, it's a feature. From the [documentation](https://developer.apple.com/documentation/swift/task/init(priority:operation:)): *Unlike Task.detached(priority:operation:), the task created by Task.init(priority:operation:) **inherits the priority and actor context of the caller**, so the operation is treated more like an asynchronous extension to the synchronous operation.* – vadian Jul 21 '23 at 19:35

1 Answers1

0

When you isolate ViewModel to the main actor, its methods and properties are isolated to the main actor, too. In fact, this practice of isolating the ObservableObject to the main actor is actually recommended in WWDC 2021’s video Discover concurrency in SwiftUI. In that video, they recommend isolating the ObservableObject being observed by a View to the main actor.

And when you use Task {…} within an actor isolated method, that creates a new top level task on the current actor (i.e., the main actor). As Vadian pointed out, the documentation says (emphasis added):

[Task {…} runs] the given nonthrowing operation asynchronously as part of a new top-level task on behalf of the current actor.

So, bottom line, you are not updating this on a “background thread”. You are updating it on the main actor. Hence, no compiler errors and no runtime warnings from the main thread checker.

Rob
  • 415,655
  • 72
  • 787
  • 1,044