Swift Concurrency 6 2

affaan-m/ECC/skills/swift-concurrency-6-2

by affaan-mef648e01899ba3e8dc6371642deaaf64b4477775No license275K starsListed Oct 9, 2026Updated Oct 9, 2026Repository updated 4 days ago

Swift 6.2 Approachable Concurrency — single-threaded by default, @concurrent for explicit background offloading, isolated conformances for main actor types. Use when adopting Swift 6.2 concurrency — offloading with @concurrent or resolving main-actor isolation.

Instructions onlySoftware Development
AI-generated overview

Guides Swift 6.2 concurrency adoption: single-threaded by default, @concurrent offloading, isolated conformances.

What it does
This skill provides reference guidance for adopting Swift 6.2's Approachable Concurrency model, where code runs single-threaded by default and background work is opted into explicitly. It explains patterns such as isolated conformances for MainActor types, MainActor default inference, protecting global and static state, and using @concurrent for CPU-intensive work. It also covers migration steps, build-setting changes, best practices, and anti-patterns. It produces explanatory guidance and code examples rather than files or scripts.
When to use it
Use it when migrating Swift 5.x or 6.0/6.1 projects to Swift 6.2, resolving data-race safety compiler errors, or designing MainActor-based app architecture. It is also relevant when offloading CPU-intensive work to background threads or implementing protocol conformances on MainActor-isolated types.
Requirements
No scripts are included; it is instructions only. Following the guidance assumes a Swift 6.2 toolchain and, for some features, Xcode 26 with Approachable Concurrency build settings enabled.

Swift 6.2 Approachable Concurrency

Patterns for adopting Swift 6.2's concurrency model where code runs single-threaded by default and concurrency is introduced explicitly. Eliminates common data-race errors without sacrificing performance.

When to Activate

  • Migrating Swift 5.x or 6.0/6.1 projects to Swift 6.2
  • Resolving data-race safety compiler errors
  • Designing MainActor-based app architecture
  • Offloading CPU-intensive work to background threads
  • Implementing protocol conformances on MainActor-isolated types
  • Enabling Approachable Concurrency build settings in Xcode 26

Core Problem: Implicit Background Offloading

In Swift 6.1 and earlier, async functions could be implicitly offloaded to background threads, causing data-race errors even in seemingly safe code:

swift
// Swift 6.1: ERROR@MainActorfinal class StickerModel {    let photoProcessor = PhotoProcessor()
    func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? {        guard let data = try await item.loadTransferable(type: Data.self) else { return nil }
        // Error: Sending 'self.photoProcessor' risks causing data races        return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)    }}

Swift 6.2 fixes this: async functions stay on the calling actor by default.

swift
// Swift 6.2: OK — async stays on MainActor, no data race@MainActorfinal class StickerModel {    let photoProcessor = PhotoProcessor()
    func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? {        guard let data = try await item.loadTransferable(type: Data.self) else { return nil }        return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)    }}

Core Pattern — Isolated Conformances

MainActor types can now conform to non-isolated protocols safely:

swift
protocol Exportable {    func export()}
// Swift 6.1: ERROR — crosses into main actor-isolated code// Swift 6.2: OK with isolated conformanceextension StickerModel: @MainActor Exportable {    func export() {        photoProcessor.exportAsPNG()    }}

The compiler ensures the conformance is only used on the main actor:

swift
// OK — ImageExporter is also @MainActor@MainActorstruct ImageExporter {    var items: [any Exportable]
    mutating func add(_ item: StickerModel) {        items.append(item)  // Safe: same actor isolation    }}
// ERROR — nonisolated context can't use MainActor conformancenonisolated struct ImageExporter {    var items: [any Exportable]
    mutating func add(_ item: StickerModel) {        items.append(item)  // Error: Main actor-isolated conformance cannot be used here    }}

Core Pattern — Global and Static Variables

Protect global/static state with MainActor:

swift
// Swift 6.1: ERROR — non-Sendable type may have shared mutable statefinal class StickerLibrary {    static let shared: StickerLibrary = .init()  // Error}
// Fix: Annotate with @MainActor@MainActorfinal class StickerLibrary {    static let shared: StickerLibrary = .init()  // OK}

MainActor Default Inference Mode

Swift 6.2 introduces a mode where MainActor is inferred by default — no manual annotations needed:

swift
// With MainActor default inference enabled:final class StickerLibrary {    static let shared: StickerLibrary = .init()  // Implicitly @MainActor}
final class StickerModel {    let photoProcessor: PhotoProcessor    var selection: [PhotosPickerItem]  // Implicitly @MainActor}
extension StickerModel: Exportable {  // Implicitly @MainActor conformance    func export() {        photoProcessor.exportAsPNG()    }}

This mode is opt-in and recommended for apps, scripts, and other executable targets.

Core Pattern — @concurrent for Background Work

When you need actual parallelism, explicitly offload with @concurrent:

Important: This example requires Approachable Concurrency build settings — SE-0466 (MainActor default isolation) and SE-0461 (NonisolatedNonsendingByDefault). With these enabled, extractSticker stays on the caller's actor, making mutable state access safe. Without these settings, this code has a data race — the compiler will flag it.

swift
nonisolated final class PhotoProcessor {    private var cachedStickers: [String: Sticker] = [:]
    func extractSticker(data: Data, with id: String) async -> Sticker {        if let sticker = cachedStickers[id] {            return sticker        }
        let sticker = await Self.extractSubject(from: data)        cachedStickers[id] = sticker        return sticker    }
    // Offload expensive work to concurrent thread pool    @concurrent    static func extractSubject(from data: Data) async -> Sticker { /* ... */ }}
// Callers must awaitlet processor = PhotoProcessor()processedPhotos[item.id] = await processor.extractSticker(data: data, with: item.id)

To use @concurrent:

  1. Mark the containing type as nonisolated
  2. Add @concurrent to the function
  3. Add async if not already asynchronous
  4. Add await at call sites

Key Design Decisions

DecisionRationale
Single-threaded by defaultMost natural code is data-race free; concurrency is opt-in
Async stays on calling actorEliminates implicit offloading that caused data-race errors
Isolated conformancesMainActor types can conform to protocols without unsafe workarounds
@concurrent explicit opt-inBackground execution is a deliberate performance choice, not accidental
MainActor default inferenceReduces boilerplate @MainActor annotations for app targets
Opt-in adoptionNon-breaking migration path — enable features incrementally

Migration Steps

  1. Enable in Xcode: Swift Compiler > Concurrency section in Build Settings
  2. Enable in SPM: Use SwiftSettings API in package manifest
  3. Use migration tooling: Automatic code changes via swift.org/migration
  4. Start with MainActor defaults: Enable inference mode for app targets
  5. Add @concurrent where needed: Profile first, then offload hot paths
  6. Test thoroughly: Data-race issues become compile-time errors

Best Practices

  • Start on MainActor — write single-threaded code first, optimize later
  • Use @concurrent only for CPU-intensive work — image processing, compression, complex computation
  • Enable MainActor inference mode for app targets that are mostly single-threaded
  • Profile before offloading — use Instruments to find actual bottlenecks
  • Protect globals with MainActor — global/static mutable state needs actor isolation
  • Use isolated conformances instead of nonisolated workarounds or @Sendable wrappers
  • Migrate incrementally — enable features one at a time in build settings

Anti-Patterns to Avoid

  • Applying @concurrent to every async function (most don't need background execution)
  • Using nonisolated to suppress compiler errors without understanding isolation
  • Keeping legacy DispatchQueue patterns when actors provide the same safety
  • Skipping model.availability checks in concurrency-related Foundation Models code
  • Fighting the compiler — if it reports a data race, the code has a real concurrency issue
  • Assuming all async code runs in the background (Swift 6.2 default: stays on calling actor)

When to Use

  • All new Swift 6.2+ projects (Approachable Concurrency is the recommended default)
  • Migrating existing apps from Swift 5.x or 6.0/6.1 concurrency
  • Resolving data-race safety compiler errors during Xcode 26 adoption
  • Building MainActor-centric app architectures (most UI apps)
  • Performance optimization — offloading specific heavy computations to background

Source and attribution

Source:affaan-m/ECCinskills/swift-concurrency-6-2at commitef648e0

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal