It's not clear what to do in the docs. My understanding is:
What I'm not sure about is if the setting of data
will be implicitly on the main actor or not.
struct ExampleView: View {
@State private var data = "Initial data"
var body: some View {
Text(data)
.onAppear {
Task {
let fetchedData = await fetchData()
await MainActor.run {
data = fetchedData // Explicitly updating on the main thread
}
}
}
}
func fetchData() async -> String {
// Simulate a network request or heavy computation
await Task.sleep(1_000_000_000) // 1 second delay
return "Updated data"
}
}
Task inherits its actor context, so from within the body here, it will be the main actor.
Correct, so data
will be set on the main actor even without MainActor.run
. A simple way to show this is to mark data
as @MainActor
, isolating it to the main actor. You will still be able to set data
in the Task
without awaiting.
Any async function may go to the co-operative thread pool, so it may run off the main thread.
If the async function is not isolated to the main actor, then yes. But so is every other piece of code that is not isolated to the main actor. You can always run code on some thread in the co-operative thread pool by using Task.detached { ... }
, or Task { ... }
in a context that isn't isolated to the main actor:
// suppose this is a global function, therefore not isolated to anything
func foo() {
Task {
// code here will run in the co-operative thread pool
}
}
Because once you touch the suspension point, it might change the context for everything after it.
No. If a task is isolated to the main actor, it will always run on the main thread. If it is isolated to some other actor, it will always run on that actor (though the thread it is running on could change because actors and threads don't have a one-to-one relationship).
Side note: you can write .task { ... }
instead of .onAppear { Task { ... } }
.