Friday, November 7, 2025

MainActor.assumeIsolated, Preconcurrency, and Isolated Conformances

Fatbobman:

As Swift 6 gradually gains adoption, this problem becomes increasingly prominent: developers want to benefit from the concurrency safety guarantees provided by the Swift compiler, while struggling with how to make their code meet compilation requirements. This article will demonstrate the clever use of MainActor.assumeIsolated in specific scenarios through an implementation case with NSTextAttachmentViewProvider.

[…]

We seem to be caught in a dilemma: we need to construct UIHostingController in MainActor, yet we cannot assign the constructed view (UIView) to self.view within MainActor.

[…]

Looking at MainActor.assumeIsolated’s signature, we can see that this API provides a MainActor context for its trailing closure. This means we can “synchronously” run code that can only execute in a MainActor context within a non-MainActor synchronous context, without creating an async environment, and return a Sendable result.

[…]

I still hope we can move past this somewhat “chaotic” transition period soon. Perhaps in a few years, when numerous official and third-party frameworks have completed their Swift 6 migration, we’ll finally enjoy a more relaxed safe concurrent programming experience.

Matt Massicotte:

I consistently find the @preconcurrency attribute to be confusing. But, I’m tired of that. Let’s just, once and for all, get a better handle how to use this thing.

[…]

It has three distinct uses. And while they all apply to definitions, the details are quite different.

Jesse Squires:

UIKit provides two diffable data source APIs, one for collections and one for tables. Recently, while working on ReactiveCollectionKit, I noticed that the APIs were updated for Swift Concurrency in the iOS 18 SDK, but the annotations were inconsistent with the documentation.

[…]

I reached out to Tyler Fox from the UIKit team on Mastodon to ask if this was a mistake. As it turns out, it is not a mistake and his reply was incredibly helpful and insightful. For posterity and documentation purposes (and because social media is ephemeral and unreliable), I’m going to reproduce his entire response here[…]

Wade Tregaskis:

These might seem pretty similar – you’d be forgiven for assuming it’s just a convenience to put @MainActor on the protocol overall rather than having to repeat it for every member of the protocol. Less error-prone, too.

But, you generally shouldn’t do that. They are not equivalent.

The first form is not merely saying that all the members of the protocol require a certain isolation, but that the type that conforms to the protocol must have that isolation. The whole type.

Matt Massicotte:

Further, as far as the compiler is concerned, there is an actor boundary both going into and returning from assumeIsolated. This means you cannot work with non-Sendable data here and that can be an enormous pain.

[…]

Before Swift 6.0, dynamic isolation was the only option. And before Swift 6.2, I think that preconcurrency conformances were the best tool for handling protocol isolation mismatches. They address pretty much all of the weakness of the nonisolated-assumeIsolated thing. But they just feel funny.

[…]

Swift 6.2 allows us to express this idea directly, by constraining a conformance to be valid only for a particular global actor.

What we now have is exactly what we want. A MainActor type that is Equatable in that context only. This is not the same as a true, unconstrainted Equatable, because those work everywhere. It’s a little like defining a new, special variant of that protocol right in line at the conformance declaration site.

[…]

But remember, not all protocols are compatible. And making this entire thing implicit makes the problems even more surprising. Don’t get me wrong, I really like isolated conformances and am very happy to see them come to the language. But they are not a magic bullet (and neither is MainActor-by-default).

Lukas Valenta at mDevCamp (Mastodon):

The talk focuses - as the name suggests - to strategy to migrate the project from Swift 5 compilation mode to Swift 6. We will discuss several issues anyone will encounter to have project that compiles under Swift 6 mode, such as issues with Combine and Async publishers, DispatchQueue.main precondition queue checks, working with older APIs that predate the concurrency, as well as a debate whether it is all worth it. I will also mention the transition of not only the project itself but also a story of external dependencies, some of which written by me, and how did the migration in the libraries took place.

Previously:

Comments RSS · Twitter · Mastodon

Leave a Comment