Thursday, November 20, 2025

(No) MainActor by Default

Matt Massicotte (Mastodon):

Swift 6.2 gives you the ability make MainActor the default isolation. Unlike the rest of the features introduced as part of “Approachable Concurrency”, this is a long-term mode. It is optional and will remain so. However, this mode is enabled for new app targets in Xcode 26. And many people take this as a very strong signal from Apple, myself included.

[…]

Well I have made up my mind, at least for now. We should not. I’m not sure we should even have the ability to do so.

[…]

But when you finally encounter concurrency, and you almost certainly will, a default of MainActor can make those encounters much more difficult to understand and address.

[…]

(I also did a whole talk on this if that’s interesting.)

I still find this all rather confusing, but I’m inclined to agree with his argument. I understood the original motivation as sort of progressive disclosure. You shouldn’t have to deal with concurrency at all if you don’t need your code to be concurrent. But for most real apps you will have to understand and use the full Swift Concurrency. If opting in to MainActor (the keyword, not the default isolation) is easier than opting out, is that really helping in the end? Why start down a garden path that leads to a cliff? And is having the simple mode worth the confusion of having two modes, and having to keep straight which module is using which? (This is on top of having separate modes for Swift 5 and 6.)

Ryan Booker:

I’ve seen quite a few people saying something similar. What worries me is that not only is MainActor the default now, the WWDC videos introducing it are quite clear that Apple think you should make your App target and UI based modules all MainActor by default, with non UI modules nonisolated by default.

So I’m worried about fighting the tide, but also the idea that you have two inverted concurrency systems to reason about in one code base if you follow Apple’s suggestion!

Jonathan Wight:

The most vocal Swift Concurrency expert outside of Apple is recommending to avoid MainActor default isolation and you’re left wondering what is even the point.

Previously:

Update (2025-11-21): Matt Massicotte:

I want to add though, that I find it really confusing to look at MainActor default as some kind of alternative path towards a 6 migration. Because it’s really just automation, it does not change the nature of the problems you have to consider.

Update (2025-12-01): Tony Arnold:

I’m trying out a MainActor by default project today, and I can’t use LocalizedStringResources that are extracted from my xcstrings catalog 😳

Antoine van der Lee:

By enabling approachable concurrency and setting the default actor isolation to MainActor, you immediately establish a safer and more predictable foundation for Swift concurrency. It also makes your project more resilient to future Swift updates and encourages a consistent mental model from day one. You can learn more about this concept in my article Approachable Concurrency in Swift 6.2: A Clear Guide.

6 Comments RSS · Twitter · Mastodon


Trying to figure out what the issue is here (I think this helps declutter a lot of code that needs not be concurrent, or concurrency is hidden behind public API that doesn't expose the concurrent implementation detail).

Apple made concurrency so convoluted and confusing, that it is now trying to make it "approachable" by hiding a lot of the convolution and confusion, but if they unconfuse it, it is going to … confuse people further? Well, isn't that the story of Swift in general.

> Changing the default isolation fundamentally alters the nature of a program's relationship with the concurrency system. It demands you now be able to recognize and understand when MainActor is inappropriate. This can be very difficult.

So we're back to the developer having to juggle the neurons they hopefully have to make concurrency work. I guess that is an undesirable end result in some Swift crowds.


Wake me up in 18 months when this shit show has hopefully settled down a bit.

In the meantime, I'll try to avoid Swift Concurrency as much as feasible.

I think I can handle one unnecessary Apple API migration per year, and I just finished migrating from StoreKit1 to StoreKit2, so Swift Concurrency can be a task for early 2027.

Lots of work, for relatively little user or developer benefit? No thanks.


I've spent the past several months working on a brand new app. And I'm using Swift, which is, in most respects, a brand new language for me. I tried SwiftUI on another project about a year ago, but ultimately had to give up on it.

This new app has some very heavy multithreading going on. Not just downloading images in the background - very heavy concurrent file I/O. I wrote it all in the old (non-approachable) Swift concurrency. I was a bit concerned about releasing a new app based on a disavowed concurrency architecture on day 1. So I bit the bullet, turn on Swift 6.2 and enabled Main actor by default. On day 2, it was done and running great.

It all seems to make perfect sense to me. Apple's APIs were always very much main-thread centric. I don't see why making MainActor the default is a bad idea. It was always the default. When I need something to really be concurrent, make it non isolated and @concurrent. It was really easy.

It is a bit amusing. I'm old enough to remember Apple's first attempt to convince people that cooperative threading was the way to go, back in the System 7 days. Then they gave us Unix and pthreads and that idea was lost to time. But there is nothing new under the sun.


> I think I can handle one unnecessary Apple API migration per year, and I just finished migrating from StoreKit1 to StoreKit2, so Swift Concurrency can be a task for early 2027.

Many of these migrations can be delayed (some can just be skipped entirely). Somtimes the thing they tell you to replace outlives the replacement. StoreKit aside IMO rushing to migrate to Apple new goodness can be a waste of time (and sometimes even formal deprecations are a waste of time). Like if you tried to get off NSCell ten years ago when they formally deprecated it you found that some things were impossible to do without it. And no NSCell stuff broke in all that time (stuff broke now with Liquid Glass and SwiftUI being shoveled underneath but that's not really exclusive to the NSCell stuff that's b/c cuz Apple wrecklessly put SwiftUI underneath AppKit and UIKit when it should always be the other way around).

So much of the comments on Mastodon is about "what Apple thinks" Apple thinks this, Apple thinks that. Apple don't give a fuck about you. Do you homie don't worry about Apple.


> Many of these migrations can be delayed (some can just be skipped entirely).

@Objc4Life in the case of StoreKit, the migration couldn’t be delayed any longer as I’m supporting a new OS 26 platform variant exclusively. This broke the Storekit1 restore purchases functionality, meaning no real choice but to migrate to Storekit2.

Swift Concurrency is likely to be in a similar position over the next few years.


@Matthew I agree on StoreKit2 migration and certain migrations are unavoidable. I've been meaning to wrap SK2 in an ObjC framework to quarantine Swift/limit its exposure in my apps but haven't got around to it yet. So much hamster wheeling I couldn't possibly keep up with all of it even if I wanted to.

My point is a lot of the stuff they try to push ends up getting killed before the thing they're trying to replace (e.g., ObjC 2.0 has outlived Swift language versions 1-5 and it wouldn't shock me if it outlives Swift 6). It doesn't always work out that way naturally but early adoption of some new somewhat controversial Apple thing is always a risk. Far as Swift Concurrency goes judging by the rapid changes and all the chaos and confusion it is causing developers trying to adopt it it seems really hard to predict how that's going to turn out in the end.

Could you share more about StoreKit1 breakage? Restore iAP isn't working? Yikes? I wasn't aware of that.

Leave a Comment