Friday, March 1, 2024

Where View.task Gets Its Main-actor Isolation From

Ole Begemann:

SwiftUI’s .task modifier inherits its actor context from the surrounding function. If you call .task inside a view’s body property, the async operation will run on the main actor because View.body is (semi-secretly) annotated with @MainActor. However, if you call .task from a helper property or function that isn’t @MainActor-annotated, the async operation will run in the cooperative thread pool.


  1. The View protocol annotates its body property with @MainActor. This transfers to all conforming types.

  2. View.task annotates its action parameter with @_inheritActorContext, causing it to adopt the actor context from its use site.

Sadly, none of these annotations are visible in the SwiftUI documentation, making it very difficult to understand what’s going on. […] To really see the declarations the compiler sees, we need to look at SwiftUI’s module interface file.


When used outside of body, there is no implicit @MainActor annotation, so task will run its operation on the cooperative thread pool by default. (Unless the view contains an @ObservedObject or @StateObject property, which somehow makes the entire view @MainActor. But that’s a different topic.)

Der Teilweise:

Swift concurrency is a mess.

Every software framework that tries to automagically do “the right thing” is doomed to break your code in the most unexpected way.

Add a new member variable and it changes execution of unrelated parts from concurrent to serial. How wild is this?

BJ Homer:

Currently, we can infer @MainActor on an entire type based on the presence of certain property wrappers within that type. TL;DR: I’m making a case that that’s a bad idea, and we should reconsider it if possible.


Comments RSS · Twitter · Mastodon

Leave a Comment