A Clear Path to Concurrency in Swift 6.2
Swift 6.2 and Xcode 26 introduce a structured and intentional approach to concurrency designed to be safer, clearer, and easier to adopt. This article explains the key concurrency concepts introduced in Swift 6.2, including new compiler attributes, feature flags, and how to configure SwiftPM projects accordingly.
Approachable Concurrency
Approachable Concurrency (SWIFT_APPROACHABLE_CONCURRENCY in Xcode 26) is a build setting that turns on a curated set of upcoming-feature flags. In Swift 6 language mode the two flags that matter are:
NonisolatedNonsendingByDefault(SE-0461) —nonisolated asyncfunctions inherit the caller’s isolation instead of hopping to the global executor.InferIsolatedConformances(SE-0470) — for global-actor-isolated types, protocol conformances are inferred as isolated to that same actor (with carve-outs when the protocol or all of its requirements are alreadynonisolated).
In Swift 5 language mode the build setting flips additional flags whose behavior is already part of Swift 6 mode by default, so they’re not relevant here.
Default @MainActor isolation is a separate setting (SWIFT_DEFAULT_ACTOR_ISOLATION, SE-0466). Xcode 26 enables both for new projects, but conceptually they are independent: you can adopt Approachable Concurrency without making @MainActor the default.
The WWDC talk Embracing Swift Concurrency presents a practical migration path from single-threaded to fully concurrent apps.
nonisolated(nonsending)
In Swift versions up to 6.1, nonisolated async methods always hopped off the caller and ran on the global executor—even when invoked from @MainActor. This introduced unexpected thread switches.
With Swift 6.2, when using the NonisolatedNonsendingByDefault flag:
nonisolated asyncmethods inherit the actor of their caller.- If called on
@MainActor, they remain on the main thread. - This avoids unintended thread switches and the resulting performance penalties.
- The explicit spelling is
nonisolated(nonsending) func ...; under the flag, plainnonisolatedon anasyncfunction means the same thing.
class Service {
nonisolated func fetchData() async {
// runs on caller's actor with NonisolatedNonsendingByDefault
}
}
@MainActor
func useService() async {
await Service().fetchData()
// Still on MainActor—no thread switch
}
InferIsolatedConformances
Before SE-0470, a global-actor-isolated type that wanted to conform to a non-isolated protocol like Equatable or Hashable had to mark each protocol requirement nonisolated explicitly:
@MainActor
class User: Equatable {
nonisolated static func == (lhs: User, rhs: User) -> Bool { /* ... */ }
}
With the InferIsolatedConformances flag, the compiler infers the conformance itself as isolated to the same global actor. The nonisolated annotation on each requirement is no longer necessary:
@MainActor
class User: Equatable {
static func == (lhs: User, rhs: User) -> Bool { /* ... */ }
// The Equatable conformance is now @MainActor-isolated.
}
@concurrent
To explicitly opt into parallel execution, Swift 6.2 introduces the @concurrent attribute:
class Worker {
@concurrent
func heavyJob() async {
// This runs in parallel on a background executor
}
}
@MainActor
func runWork() async {
await Worker().heavyJob()
// Resumes on MainActor after completion
}
@concurrent implies nonisolated, so writing both is redundant. It is the explicit alternative to the new nonisolated(nonsending) default: instead of inheriting the caller’s isolation, the function is dispatched to the global concurrent executor. @concurrent and nonisolated(nonsending) are mutually exclusive on the same declaration.
SwiftPM Configuration
In Xcode 26 you toggle Approachable Concurrency in the build settings. For a Swift package, configure it explicitly along with the language mode and—optionally—default actor isolation:
// swift-tools-version: 6.2
for target in package.targets {
var settings = target.swiftSettings ?? []
settings.append(.swiftLanguageMode(.v6))
settings.append(.defaultIsolation(MainActor.self))
settings.append(.enableUpcomingFeature("NonisolatedNonsendingByDefault"))
settings.append(.enableUpcomingFeature("InferIsolatedConformances"))
target.swiftSettings = settings
}
A few notes on this setup:
swift-tools-version: 6.2is required fordefaultIsolationand these upcoming features to be available.- Both
NonisolatedNonsendingByDefaultandInferIsolatedConformancesare upcoming features—useenableUpcomingFeature, notenableExperimentalFeature. swiftLanguageMode(.v6)enables strict concurrency automatically; no separateStrictConcurrencyflag is needed.defaultIsolation(MainActor.self)is independent of Approachable Concurrency—drop it if you don’t want@MainActoras the module-wide default.
How It All Works Together
- Single-threaded by default:
defaultIsolation(MainActor.self)makes unannotated code MainActor-isolated, keeping UI work safe out of the box. - Predictable async: with
NonisolatedNonsendingByDefault,nonisolated asyncfunctions stay on the caller’s actor instead of hopping to a background thread. - Explicit parallelism:
@concurrentis the deliberate opt-out that dispatches a function to the global concurrent executor. - Strict safety: Swift 6 mode enforces
Sendableand actor-isolation rules at compile time. - Isolated conformances:
InferIsolatedConformanceslets an@MainActortype conform to non-isolated protocols likeEquatablewithoutnonisolatedworkarounds.
References
- Embracing Swift Concurrency (WWDC 2025)
- Swift Evolution Vision: Approachable Concurrency
- SE-0461 — Run nonisolated async functions on the caller’s actor by default
- SE-0466 — Control default actor isolation inference
- SE-0470 — Global-actor isolated conformances
- Ole Begemann’s Gist: swift-list-features.sh
Update
This article has been updated to reflect the current state of Swift 6.2 and Xcode 26. The terminology, feature-flag classifications, and SwiftPM configuration have been brought in line with the official Swift Evolution proposals (SE-0461, SE-0466, SE-0470) and the Approachable Concurrency vision document.