Monday, November 3, 2025

Swift 6.2: Approachable Concurrency

Holly Borla:

Swift 6.2 lowers the barrier to concurrent programming with a set of changes designed to reduce boilerplate and let you write safe concurrent code more naturally:

  • Single-threaded by default: Run your code on the main thread without explicit @MainActor annotations using the new option to isolate code to the main actor by default. This option is ideal for scripts, UI code, and other executable targets.
  • Intuitive async functions: Write async code without concurrent access to mutable state. Previously, nonisolated async methods always switched to the global executor that manages the concurrent thread pool, which made it difficult to write async methods for class types without data-race safety errors. In Swift 6.2, you can migrate to an upcoming feature where async functions run in the caller’s execution context, even when called on the main actor.
  • Opting into concurrency with @concurrent: Introduce code that runs concurrently using the new @concurrent attribute. This makes it clear when you want code to remain serialized on actor, and when code may run in parallel.

Donny Wals:

With Swift 6.2, Apple has made a several improvements to Swift Concurrency and its approachability. One of the biggest changes is that new Xcode projects will now, by default, apply an implicit main actor annotation to all your code. This essentially makes your apps single-threaded by default.

I really like this change because without this change it was far too easy to accidentally introduce loads of concurrency in your apps.

In this post I’d like to take a quick look at how you can control this setting as well as the setting for nonisolated(nonsending) from Xcode 26’s build settings menu.

Donny Wals:

Xcode 26 allows developers to opt-in to several of Swift 6.2’s features that will make concurrency more approachable to developers through a compiler setting called “Approachable Concurrency” or SWIFT_APPROACHABLE_CONCURRENCY. In this post, we’ll take a look at how to enable approachable concurrency, and which compiler settings are affected by it.

Matt Massicotte:

“Approachable Concurrency” is just a big group of settings. It is completely independent of making the default isolation MainActor.

Matt Massicotte:

In the Swift 6 language mode, there are only two flags “Approachable Concurrency” changes: InferIsolatedConformances and NonisolatedNonsendingByDefault. It switches on more stuff in 5 mode, but those will not affect this material.

Paul Hudson (Mastodon, Hacker News):

SE-0466 introduces the ability for code to opt into running on a single actor by default – to effectively go back to being a single-threaded program, where most code runs on the main actor until you say otherwise.

[…]

SE-0470 resolves a small but important concurrency problem by making it possible to restrict a protocol conformance to a specific global actor.

[…]

SE-0472 introduces a new way to create tasks so they start immediately if possible, rather than the existing behavior that only allows tasks to be queued to run at the next opportunity.

[…]

SE-0461 adjusts the way nonisolated async functions are called so that they run on the same actor as their caller. This sounds like a really abstract change, but it is important so I would recommend you spend the time to understand what’s changing and why.

[…]

SE-0371 introduces the ability to mark the deinitializers of actor-isolated classes as being isolated, which allows them to safely access data elsewhere in the class.

[…]

SE-0462 introduces the ability for tasks to detect when their priority has been escalated, and also for us to manually escalate task priority if needed.

[…]

SE-0469 introduces a useful change to the way we create tasks and child tasks: we can now give them names, which is ideal for debugging when one particular task goes rogue.

Matt Massicotte:

As we started getting closer to the release of Swift 6.0, I had this bright idea. I decided to write about every evolution proposal related to concurrency that would ship with that release. This resulted in 12 posts and let me tell you, it was a lot of work.

[…]

I don’t think I can pull off a post for each proposal this year. But, I’m not sure that’s even necessary anyways. Many of them are minor things. But not all.

[…]

I wanted to go on this little side-quest because people don’t find understanding isolation easy. Adding the distinction between static vs dynamic just makes it that much harder. And I really wanted to find a way to help explain this, because this is the Big Idea you need to get to understand what’s changing.

[…]

Setting the default has the potential to completely remove the need to think about concurrency. This could be an enormous win in many situations. For example, it would make the Swift 6 language mode a more friendly choice for true beginner programmers.

[…]

Right now, though, I see myself sticking with a nonisolated default. I need more experience trying this out with real systems before I make up my mind.

Steve Troughton-Smith:

As I understand it, the whole point of Swift 6 is to add syntactic sugar all over your projects to try to codify ahead of time that you know which thread everything will run on. The ‘approachable concurrency’ changes in 6.2 feels like an admission that nobody is actually going to do that, because it’s too hard, if not impossible, for most apps. It’s a major lift and a lot of work that, at best, leaves your app the same as before you started 😅

Approachable Concurrency, instead of making you annotate everything piece by piece, flips it and just assumes that none of your app is concurrent unless you specify otherwise, which is absolutely what should have been the default for Swift 6. If you were halfway through a Swift 6 port under the previous model, you now… delete all your code and go back to how it was before you annotated it?

Donny Wals:

Finding the solution to the issues I describe above is pretty tedious, and it forces us to explicitly opt-out of concurrency for specific methods and eventually an entire class. This feels wrong. It feels like we’re having to decrease the quality of our code just to make the compiler happy.

In reality, the default in Swift 6.1 and earlier was to introduce concurrency by default. Run as much as possible in parallel and things will be great.

This is almost never true. Concurrency is not the best default to have.

Amy Worrall:

imo the thing they tried to make safe is not the thing that’s actually the problem for most devs, and they did it in such a way that you can’t make a halfway house solution. Especially when you’re interacting with other people’s code, sometimes there’s just no way to architect things how you want.

Matt Massicotte:

I do not think you need to go and read every proposal. I did, and honestly, it was a lot of work. But, something I learned from doing it is many of these new language features were specifically built to help hide details. It’s still pretty early, but I think ultimately this is going to end up largely succeeding. What it all comes down to is how much concurrency you have in your project. (Probably too much)

You cannot progressively disclose the details of features you are actively using. Concurrency touches virtually all existing Swift code so tons of stuff gets shoved in your face all at once.

[…]

But, accidentally running stuff off the main thread is the simplest and most straight-forward concurrency problem you could possibly have it was absolutely pervasive.

[…]

Now there are some well-established patterns that can help avoid many concurrency issues, that one definitely included. But, these are often employed by advanced users that have been practicing for years. I think it is easy for these habits to make “I don’t have this problem” feel like “this is not a problem”.

[…]

Actors are not queues. Let me say it again, actor is an advanced tool. Be wary of any material that makes it seem otherwise. You should be sticking to the binary main/not-main until you are very comfortable with isolation before trying them out.

travisgriggs:

One of the pitches in the earlier days was “C/Objective-C OK, but you can’t write safe/next level code with it—-Swift will close that gap.”

N years later, it doesn’t feel like there has been a step change in Apple software quality; if anything Apple software feels less solid, and looks cool “look what I did” extension points. I mean, some of the [things] you could do with runtime categories, and runtime prototypes were really cool. Now when I work on my 2 apps that originally happily port to Swift/UIKit, I’m just left confused with how to make things work. I’m happy when it finally works, and don’t ever try to improve the thing, it’s too much work.

Steve Troughton-Smith:

Swift 6 language mode is still a no-go, even with approachable concurrency turned on. I would be surprised if any full-featured UIKit app is able to use it, even now. Toy apps, or something that stays entirely within SwiftUI-land, maybe. It’s a major lift.

Jacob Bartlett (ios_memes):

Is Swift 6 strict concurrency going to be our Python 3 moment?

Sean Heber:

So I now have Tapestry entirely compiling with Swift 6 mode.

That was… fun.

Daniel Jalkut:

I achieved a major development milestone for my biggest app, MarsEdit, today. I can now build against Swift 6 with strict concurrency, and no warnings. It was harder than it should be (though Apple’s working on that), but it feels good knowing I can move forward with concurrent confidence.

Gwendal Roué:

GRDB is in a strange bucket. I think it goes back to the introduction of Swift concurrency.

[…]

Want to use the new MainActor isolation by default? It’s the default for new Xcode 26 projects, after all. Well this new language dialect creates incomprehensible compiler errors, and is so incompatible with the normal language that I’m supposed to rewrite all sample codes and documentation (hint: I won’t, SE-0466 is a sick joke).

Want to await SQLite with non-Sendable types? Well, I’m not sure the language makes it possible to express that.

Nico Reese (forum):

What would I do about this?

“Main actor-isolated property ‘timestamp’ can not be mutated from a Sendable closure”

[…]

It seems to be an issue with Core Data not being 100% ready for these concurrency changes? The workaround gets rid of the warning but does not look very nice.

Dave DeLong:

It’s hard to look at the state of @swiftlang’s concurrency model these days and draw any other conclusion than “this is a hodge podge of conflicting models that are basically inoperable together”.

IMO Swift Concurrency needs a huge “reset” button. Throw it all away and start over.

Ryan Ashcraft:

All I want from Swift is a fast compiler that gives actually useful error messages. Enough syntax sugar. Oh and also, Swift Concurrency has been a disaster.

Kyle Hughes:

It doesn’t feel like any plane has been landed in the realm of Apple platform development since the introduction of SwiftUI in 2019. Nothing new introduced fits well together. Everything that is released feels unfinished and gets informally deprecated, only to be replaced with another incomplete solution. It’s a confusing moment to be in—we will likely reflect on this as the time where we should have known what direction we were headed in.

Modern Apple platform development is optimized for building miniature versions of Apple’s own apps or for tackling interesting programming-language puzzles. It is, at present, poorly suited to making strategic, long-term decisions about economically viable software—or to providing the tools needed to make such decisions.

That gap will either get solved or not, but regardless, the end of this narrative arc is likely in the air right now.

Previously:

1 Comment RSS · Twitter · Mastodon


"Intuitive async functions"

So they basically reinvented

dispatch_async(dispatch_get_main_queue(), ^{
	// Magic that happens just after the layout pass
});

Just with a lot more cruft and ugliness.

Leave a Comment