Senior iOS Software Engineer

Cologne, Germany

A Clear Path to Concurrency in Swift 6.2

Last updated:

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 async functions 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 already nonisolated).

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 async methods 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, plain nonisolated on an async function 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.2 is required for defaultIsolation and these upcoming features to be available.
  • Both NonisolatedNonsendingByDefault and InferIsolatedConformances are upcoming features—use enableUpcomingFeature, not enableExperimentalFeature.
  • swiftLanguageMode(.v6) enables strict concurrency automatically; no separate StrictConcurrency flag is needed.
  • defaultIsolation(MainActor.self) is independent of Approachable Concurrency—drop it if you don’t want @MainActor as the module-wide default.

How It All Works Together

  1. Single-threaded by default: defaultIsolation(MainActor.self) makes unannotated code MainActor-isolated, keeping UI work safe out of the box.
  2. Predictable async: with NonisolatedNonsendingByDefault, nonisolated async functions stay on the caller’s actor instead of hopping to a background thread.
  3. Explicit parallelism: @concurrent is the deliberate opt-out that dispatches a function to the global concurrent executor.
  4. Strict safety: Swift 6 mode enforces Sendable and actor-isolation rules at compile time.
  5. Isolated conformances: InferIsolatedConformances lets an @MainActor type conform to non-isolated protocols like Equatable without nonisolated workarounds.

References

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.