Senior iOS Softwareentwickler

Köln, Deutschland

A Clear Path to Concurrency in Swift 6.2

Sprache: Dieser Artikel ist auf Englisch verfasst.

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 is a build mode introduced in Xcode 26 and SwiftPM for Swift 6.2. It offers:

  • Default @MainActor isolation for all declarations, ensuring that most code runs on the main thread.
  • Predictable async behavior, without implicit thread jumps.

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 were always detached to a 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 behavior is more intuitive and avoids unintended performance penalties.
class Service {
    func fetchData() async { /* nonisolated(nonsending) by default */ }
}

@MainActor
func useService() async {
    await Service().fetchData()
    // Still on MainActor—no thread switch
}

@concurrent

To explicitly opt into parallel execution for methods, Swift 6.2 introduces the @concurrent attribute:

class Worker {
    @concurrent
    nonisolated func heavyJob() async {
        // This runs in parallel on a background executor
    }
}

@MainActor
func runWork() async {
    await Worker().heavyJob()
    // Resumes on MainActor after completion
}

@concurrent overrides nonisolated(nonsending) and ensures the method runs concurrently, suitable for CPU-intensive work.

Supporting Concepts: nonisolated, Sendable, and StrictConcurrency

  • nonisolated removes actor-based scheduling obligations for a method, allowing it to be invoked from any context without implicit await or thread hops.
  • Sendable is a protocol that marks types as safe for cross-actor or cross-thread transfer.
  • Enabling the flag StrictConcurrency enforces compile-time checks for Sendable, actor isolation, and concurrency rules.

These help define clear concurrency boundaries and enforce safety at compile time.

SwiftPM Configuration

To enable all of the above in a Swift package, use this SwiftPM configuration:

for target in package.targets {
    var settings = target.swiftSettings ?? []
    settings.append(.defaultIsolation(MainActor.self))
    settings.append(.enableExperimentalFeature("StrictConcurrency"))
    settings.append(.enableExperimentalFeature("NonisolatedNonsendingByDefault"))
    settings.append(.enableExperimentalFeature("InferIsolatedConformances"))
    target.swiftSettings = settings
}

Feature Summary

  • defaultIsolation(MainActor.self): sets default actor isolation
  • StrictConcurrency: enforces Sendable and concurrency safety
  • NonisolatedNonsendingByDefault: applies nonisolated(nonsending) behavior
  • InferIsolatedConformances: allows protocol conformances in actor-isolated contexts without global isolation

Thanks to Ole Begemann’s Gist for listing these flags via the Swift compiler’s hidden feature list.

How It All Works Together

  1. Single-threaded execution: default @MainActor ensures UI safety.
  2. Predictable async: nonisolated(nonsending) keeps async code on the calling actor.
  3. Explicit parallelism: @concurrent enables conscious offloading to background executors.
  4. Strict safety: Sendable, actors, and compiler enforcement via StrictConcurrency.
  5. Infer isolated conformances: allows isolated protocol implementations without broad isolation impact.

This provides a clear, staged concurrency model with control and visibility.

References