I was trying out the new async/await pattern in swift and I came accross something which I found it confusing.
struct ContentView: View {
var body: some View {
Text("Hello World!")
.task {
var num = 1
Task {
print(num)
}
Task {
print(num)
}
}
}
func printScore() async {
var score = 1
Task { print(score) }
Task { print(score) }
}
}
Can someone please clarify looking at the above screenshot on why the compiler only complaints about captured var
inside printScore()
function and does not complaint when the same is being done using the task
modifier on the body
computed property of the ContentView
struct (i.e Line 14-24) ?
This is the example I came up with and got confused on the compiler behavior.I also change the compiler setting "Strict Concurrency Checking” build setting to “Complete” and still don't see the compiler complaining.
This is a special power of @MainActor
. In a View, body
is tagged MainActor:
@ViewBuilder @MainActor var body: Self.Body { get }
Tasks inherit the context of their caller, so those are are also MainActor. If you replace Task
with Task.detached
in body
, you will see the same error, since this will move the Task out of the MainActor context.
Conversely, if you add @MainActor
to printScore
it will also compile without errors:
@MainActor func printScore() async {
var score = 1
Task { print(score) }
Task { print(score) }
}
score
inherits the MainActor designation, and is protected. There is no concurrent access to score
, so this is fine and correct.
This actually applies to all actors, but I believe the compiler has a bug that makes things not quite behave the way you expect. The following code compiles:
actor A {
var actorVar = 1
func capture() {
var localVar = 1
Task {
print(actorVar)
print(localVar)
}
}
}
However, if you remove the reference to actorVar
, the closure passed to Task will not be put into the actor's context, and that will make the reference to localVar
invalid. IMO, this is a compiler bug.