Post not yet marked as solved
Post marked as unsolved with 0 replies, 1,518 views
In the session Discover concurrency in SwiftUI (at 19:48), the following sample code is presented as an example of starting an async task from a Button action (which is synchronous):
struct SavePhotoButton: View {
var photo: SpacePhoto
@State private var isSaving = false
var body: some View {
Button {
Task {
isSaving = true
await photo.save()
isSaving = false
}
} label: {
Text("Save")
// …
}
// …
}
}
(The code on the slide uses async { … }. I replaced this with the current Task { … } syntax.)
I'm wondering if manipulating view state from inside the task closure like this is allowed. In fact, when you compile this with -Xfrontend -warn-concurrency, you get compiler warnings on all three lines in the task closure:
Task {
// warning: Cannot use parameter 'self' with a non-sendable type 'SavePhotoButton' from concurrently-executed code
isSaving = true
// same warning
await photo.save()
// same warning
isSaving = false
}
You have to mark the view as @MainActor to get rid of the warnings:
@MainActor
struct SavePhotoButton: View { … }
Questions:
Can you confirm that the sample code is invalid without the @MainActor annotation on the view?
How does the Task { … } closure guarantees that it runs on the main actor. I know that Task { … } inherits the current actor execution context, but how does that work here?
My guess:
View.body is annotated with @MainActor in the SwiftUI module interface
Actor context inheritance is based on the lexical scope, so the fact that the Task { … } closure is inside body is enough for it to inherit that context, even if it's called from another context.
Is this correct?
Thanks.