When marking the whole class with MainActor, it shows an error on self.clickedCount += 1
@MainActor
class MyButton: @unchecked Sendable {
var clickedCount = 0
func onClicked() {
doSomethingThenFollowUp {
self.clickedCount += 1 // `main actor-isolated property 'clickedCount' can not be mutated from a Sendable` and `mutation of this property is only permitted within the actor`
}
}
}
But if I mark MainActor on the method of onClicked instead of the whole type, it will stop the error.
As my understanding, when marking the whole type with global actor annotation, its methods become to run on the same global actor.
Am I missing something?
The closure argument to doSomethingThenFollowUp is not automatically tied to the class's isolation context. I think it can't be, because the parameter is sendable and escaping, with no particular isolation requirements, so that function is free to run the closure argument anywhere (irrespective of whether the function itself is tied to the main actor, it could pass the closure off to something which is not).
You can force your closure argument to also be tied to the main actor like:
doSomethingThenFollowUp { @MainActor in
self.clickedCount += 1
}
…though you might still have to address further complaints from the compiler (i.e. doSomethingThenFollowUp needs to actually ensure the closure is only invoked from the main actor).
It's not about the global actor annotation on onClicked, it's about var clickedCount. In your first example, clickedCount is @MainActor-isolated. In your second example, clickCount is not isolated. In both cases, the closure to doSomethingThenFollowUp is nonisolated, so it's only an error in the first case where clickCount is isolated to the main actor.
The closure argument is passed to body which has type @escaping @Sendable () -> Void. This function type is not isolated to the main actor, and in the implementation of doSomethingThenFollowUp, it's not called on the main actor -- it's called in a detached task, which is on the global concurrent thread pool, aka the "generic executor". If you annotate body as @MainActor, you would get a compiler error:
@MainActor func doSomethingThenFollowUp(_ body: @escaping @MainActor @Sendable () -> Void) {
Task.detached {
body() // error: Expression is 'async' but is not marked with 'await'
}
}