SuperAPI AI Directory

swift-concurrency-6-2

Coding & development

View repository

Updated 10-16 07:23

About this skill

Swift 6.2 Approachable Concurrency

Patterns adopting the Swift 6.2 concurrency model: code runs single-threaded by default, and concurrency must be introduced explicitly. Eliminates common data-race errors without sacrificing performance.

When to Activate

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

Core Problem: Implicit Background Offloading

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

// Swift 6.1: 错误 (ERROR)
@MainActor
final 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 }

        // 错误:发送 'self.photoProcessor' 存在引起数据竞争的风险
        return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
    }
}

Swift 6.2 fixes this: async functions stay on the caller's execution actor by default.

// Swift 6.2: 正常 (OK) — 异步保留在 MainActor,无数据竞争
@MainActor
final 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 safely conform to nonisolated protocols:

protocol Exportable {
    func export()
}

// Swift 6.1: 错误 (ERROR) — 跨入主执行角色隔离的代码
// Swift 6.2: 正常 (OK),使用隔离一致性
extension StickerModel: @MainActor Exportable {
    func export() {
        photoProcessor.exportAsPNG()
    }
}

The compiler ensures this conformance is only used on the Main Actor:

// 正常 (OK) — ImageExporter 也是 @MainActor
@MainActor
struct ImageExporter {
    var items: [any Exportable]

    mutating func add(_ item: StickerModel) {
        items.append(item)  // 安全:相同的执行角色隔离
    }
}

// 错误 (ERROR) — 非隔离上下文不能使用 MainActor 一致性
nonisolated struct ImageExporter {
    var items: [any Exportable]

    mutating func add(_ item: StickerModel) {
        items.append(item)  // 错误:此处无法使用 Main actor 隔离的一致性
    }
}

Core Pattern — Global and Static Variables

Use MainActor to protect global/static state:

// Swift 6.1: 错误 (ERROR) — 非 Sendable 类型可能具有共享的可变状态
final class StickerLibrary {
    static let shared: StickerLibrary = .init()  // 错误
}

// 修复:添加 @MainActor 注解
@MainActor
final class StickerLibrary {
    static let shared: StickerLibrary = .init()  // 正常 (OK)
}

MainActor Default Inference Mode

Swift 6.2 introduces a mode that infers MainActor by default — no manual annotations needed:

// 启用 MainActor 默认推断后:
final class StickerLibrary {
    static let shared: StickerLibrary = .init()  // 隐式为 @MainActor
}

final class StickerModel {
    let photoProcessor: PhotoProcessor
    var selection: [PhotosPickerItem]  // 隐式为 @MainActor
}

extension StickerModel: Exportable {  // 隐式为 @MainActor 一致性
    func export() {
        photoProcessor.exportAsPNG()
    }
}

This mode is optional and recommended for apps, scripts, and other executable targets.

Core Pattern — Background Work with @concurrent

When you need true parallelism, use @concurrent to explicitly offload:

Important Note: This example requires the Approachable Concurrency build settings — SE-0466 (MainActor default isolation) and SE-0461 (nonisolated(nonsending) by default). With these settings enabled,extractSticker stays on the caller's execution actor, making mutable state access safe.Without these settings, this code would have a data race — the compiler will report an error for it.

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
    }

    // 将高耗时工作卸载到并发线程池
    @concurrent
    static func extractSubject(from data: Data) async -> Sticker { /* ... */ }
}

// 调用方必须使用 await
let processor = PhotoProcessor()
processedPhotos[item.id] = await processor.extractSticker(data: data, with: item.id)

Steps for using @concurrent :

  1. Mark the containing type as nonisolated
  2. Add @concurrent
  3. to the function async
  4. If not already async, add await

Key Design Decisions

Decision Rationale
Single-threaded by default Most naturally written code is free of data races; concurrency is optional
Async stays on the calling execution actor Eliminates implicit offloading that causes data race errors
Isolated conformances MainActor types can conform to protocols without unsafe workarounds
@concurrent explicit opt-in Background execution is a deliberate performance choice, not an accident
MainActor default inference Reduces redundant @MainActor annotations in app targets
Opt-in adoption Non-destructive migration path — enable features incrementally

Migration steps

  1. Enable in Xcode: Swift Compiler > Concurrency section in Build Settings
  2. Enable in SPM: use in the package manifest SwiftSettings API
  3. Use migration tools: automated code changes via swift.org/migration
  4. Start with MainActor by default: enable inferred mode for App targets
  5. Add where needed @concurrent: Profile first, then offload hot paths
  6. Test thoroughly: data race issues will become compile-time errors

Best practices

  • Start with MainActor — write single-threaded code first, optimize later
  • Use only for CPU-intensive work @concurrent — image processing, compression, complex computations
  • Enable MainActor inference mode for App targets that are mostly single-threaded
  • Profile before offloading — use Instruments to find real bottlenecks
  • Use MainActor to protect global variables — global/static mutable state requires actor isolation
  • Use isolated conformances, not nonisolated workarounds or @Sendable wrappers
  • Incremental migration — enable one feature at a time in Build Settings

Anti-Patterns to avoid

  • Apply to every async function @concurrent(most functions don't need background execution)
  • Use without understanding isolation nonisolated to suppress compiler errors
  • Keep the old when actors provide the same safety DispatchQueue patterns
  • Skip in concurrency-related Foundation Models code model.availability checks
  • Fight the compiler — if it reports a data race, the code has a real concurrency problem
  • Assume all async code runs in the background (Swift 6.2 default: stays on the calling actor)

When to use

  • All new Swift 6.2+ projects (recommended default: "easy concurrency")
  • Migrate existing Apps from Swift 5.x or 6.0/6.1 concurrency
  • Resolve data race safety compiler errors during Xcode 26 adoption
  • Build application architecture centered on MainActor (most UI Apps)
  • Performance optimization — offload specific heavy computations to the background