Thursday, May 12, 2022

@MainActor Not Guaranteed

Ole Begemann:

@MainActor is a Swift annotation to coerce a function to always run on the main thread and to enable the compiler to verify this. How does this work? In this article, I’m going to reimplement @MainActor in a slightly simplified form for illustration purposes, mainly to show how little “magic” there is to it. The code of the real implementation in the Swift standard library is available in the Swift repository.


@MainActor already uses the unofficial ability for an actor to provide a custom executor, and we’re going to do the same for our reimplementation. A serial executor that runs its job on the main dispatch queue is implemented as follows.


John McCall’s draft proposal for custom executors is worth reading, particularly the philosophy section.

Rob Jonson (tweet):

For good measure – I mark the task as @MainActor.

My expectation was the following

  1. in doWork(), it would return off the main thread after Background().go()
  2. @MainActor annotation would ensure that calling self.mainDate would happen on the main thread – or there would be a compiler error
  3. @MainActor annotation would ensure that calling self.storedDate would happen on the main thread – or there would be a compiler error

#2 and #3 are false.


Bizarrely – If include a print statement in my task, then everything does run on the main thread!!!

Ole Begemann:

If you’re writing Swift concurrency code, add these compiler flags:

-Xfrontend -warn-concurrency -Xfrontend -enable-actor-data-race-checks

(in Xcode: Other Swift Flags)

Warnings in Swift 5.5 identify unsafe constructs, will become errors in Swift 6.

Rob Jonson:

Moving the exact same code out of the NSViewController and into a separate class (that inherits from nothing) causes the expected compiler warnings to kick in.

It seems that my model is ‘Sendable’ – so now the compiler can do magic.

This feels like an important limitation that should be in BIG BOLD LETTERS in the documentation…

See also: @MainActor ignored by the compiler.

Update (2022-05-13): See also: Frank Illenberger.

2 Comments RSS · Twitter

It's always an honour to be quoted here. I thought I should go back and re-run my example code

Running the example code in Xcode 13.3.1 on OSX 12.3.1; The code behaves as expected. (e.g. it is no longer surprising/broken)

I don't know if this is a compiler fix (so newly compiled apps are fine), or an OS fix (so your app will only behave unexpectedly on older OS's.)

I still think there is a glaring lack of documentation around what @MainActor actually promises.

@Rob Thanks for the update! I agree that it should be documented better.

Leave a Comment