How it works
The explanation, with examples you can run.
Isolation domains
- Actor-isolated: code inside an
actor, or marked with a global actor such as@MainActor. Only one piece of an actor's code runs at a time, so its state can't race. - Nonisolated: code that belongs to no actor. It can't touch actor state directly.
Calling into another domain is a potential suspension and needs await. Values passed across must be Sendable: value types made of Sendable parts, actors, immutable final classes, or types that do their own locking and say so with @unchecked Sendable.
struct Order: Sendable { let id: Int; var total: Double } // value type: safe to send actor Ledger { private var orders: [Order] = [] func record(_ order: Order) { orders.append(order) } // isolated to this actor var count: Int { orders.count }} @MainActorfunc checkout(ledger: Ledger) async { await ledger.record(Order(id: 1, total: 9.5)) // hop to the actor and back print("orders:", await ledger.count)} await checkout(ledger: Ledger())Regions and sending
Requiring Sendable everywhere would reject a lot of safe code, such as creating a non-Sendable object and handing it off. Region-based isolation (SE-0414, Swift 6.0) lets the compiler track groups of values that refer to each other: if a non-Sendable value's whole region is passed to another domain and never used again by the sender, the transfer is allowed. A sending parameter or result (SE-0430) states that contract in a signature; Task { } and continuations use it.
Swift 6.2: approachable concurrency
Swift 6.2 added two new defaults, both opt-in per module (Xcode 26 shows them as the Approachable Concurrency and Default Actor Isolation build settings):
- Nonisolated async functions run on the caller's actor (SE-0461, upcoming feature
NonisolatedNonsendingByDefault). Before, a nonisolatedasyncfunction always hopped to the global concurrent pool, so calling one from the main actor meant sending its arguments across. Now it isnonisolated(nonsending)by default; write@concurrenton a function that should really run off the actor, such as heavy decoding. - Default actor isolation (SE-0466): a module can be compiled with
-default-isolation MainActor(.defaultIsolation(MainActor.self)in a package), so unannotated code is main-actor isolated. For UI apps, which are mostly single-threaded, this removes most annotations; background work is then marked explicitly.
These change behaviour, not just diagnostics: the same code can run on a different executor depending on the settings. Check a module's settings before reasoning about where a function runs.
Other pieces
isolatedparameters (func log(on actor: isolated Ledger)) run a function in the domain of a given actor, and#isolationpasses the caller's isolation along.- Global actors other than
@MainActorcan be declared with@globalActor. nonisolated(unsafe)opts a variable out of checking; it is an escape hatch, like@unchecked Sendable.- Swift 6.4 adds
withTaskCancellationShield(SE-0504) for cleanup that must finish even when the task is cancelled.
The migration guide on swift.org explains how to move a module to the Swift 6 language mode one warning category at a time.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.