swift-concurrency-6-2

swift-concurrency-6-2

热门

Swift 6.2 平易近人的并发模型——默认单线程执行,使用 @concurrent 显式后台卸载,为主 actor 类型提供隔离协议遵循。

23万Star
3.5万Fork
更新于 2026/7/17
SKILL.md
readonly只读
name
swift-concurrency-6-2
description

Swift 6.2 平易近人的并发模型——默认单线程执行,使用 @concurrent 显式后台卸载,为主 actor 类型提供隔离协议遵循。

Swift 6.2 平易近人的并发

采用 Swift 6.2 并发模型的模式,代码默认单线程执行,并发显式引入。消除常见的数据竞争错误,同时不牺牲性能。

何时启用

  • 将 Swift 5.x 或 6.0/6.1 项目迁移到 Swift 6.2
  • 解决数据竞争安全的编译器错误
  • 设计基于 MainActor 的应用架构
  • 将 CPU 密集型工作卸载到后台线程
  • 在 MainActor 隔离的类型上实现协议遵循
  • 在 Xcode 26 中启用平易近人的并发构建设置

核心问题:隐式后台卸载

在 Swift 6.1 及更早版本中,async 函数可能被隐式卸载到后台线程,即使在看似安全的代码中也会导致数据竞争错误:

// Swift 6.1: 错误
@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 修复了这个问题:async 函数默认停留在调用 actor 上。

// Swift 6.2: 正确——async 停留在 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)
    }
}

核心模式——隔离协议遵循

MainActor 类型现在可以安全地遵循非隔离协议:

protocol Exportable {
    func export()
}

// Swift 6.1: 错误——跨越到主 actor 隔离代码
// Swift 6.2: 正确,使用隔离协议遵循
extension StickerModel: @MainActor Exportable {
    func export() {
        photoProcessor.exportAsPNG()
    }
}

编译器确保该协议遵循仅在主 actor 上使用:

// 正确——ImageExporter 也是 @MainActor
@MainActor
struct ImageExporter {
    var items: [any Exportable]

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

// 错误——非隔离上下文不能使用 MainActor 协议遵循
nonisolated struct ImageExporter {
    var items: [any Exportable]

    mutating func add(_ item: StickerModel) {
        items.append(item)  // 错误:此处不能使用 MainActor 隔离的协议遵循
    }
}

核心模式——全局变量和静态变量

使用 MainActor 保护全局/静态状态:

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

// 修复:使用 @MainActor 注解
@MainActor
final class StickerLibrary {
    static let shared: StickerLibrary = .init()  // 正确
}

MainActor 默认推断模式

Swift 6.2 引入了一种模式,默认推断 MainActor——无需手动注解:

// 启用 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()
    }
}

此模式为可选启用,推荐用于应用、脚本和其他可执行目标。

核心模式——使用 @concurrent 进行后台工作

当需要真正的并行时,使用 @concurrent 显式卸载:

重要: 此示例需要平易近人的并发构建设置——SE-0466(MainActor 默认隔离)和 SE-0461(默认非隔离非发送)。启用这些设置后,extractSticker 停留在调用者的 actor 上,使得可变状态访问安全。如果没有这些设置,此代码存在数据竞争——编译器会标记它。

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)

使用 @concurrent

  1. 将包含类型标记为 nonisolated
  2. 在函数上添加 @concurrent
  3. 如果尚未异步,添加 async
  4. 在调用点添加 await

关键设计决策

决策 理由
默认单线程 最自然的代码无数据竞争;并发是可选加入的
Async 停留在调用 actor 上 消除导致数据竞争错误的隐式卸载
隔离协议遵循 MainActor 类型可以遵循协议,无需不安全的变通方法
@concurrent 显式可选加入 后台执行是刻意的性能选择,而非偶然
MainActor 默认推断 减少应用目标的 @MainActor 样板注解
可选加入采用 非破坏性迁移路径——逐步启用功能

迁移步骤

  1. 在 Xcode 中启用:构建设置中的 Swift 编译器 > 并发部分
  2. 在 SPM 中启用:在包清单中使用 SwiftSettings API
  3. 使用迁移工具:通过 swift.org/migration 自动更改代码
  4. 从 MainActor 默认开始:为应用目标启用推断模式
  5. 在需要的地方添加 @concurrent:先分析性能,然后卸载热点路径
  6. 彻底测试:数据竞争问题变为编译时错误

最佳实践

  • 从 MainActor 开始——先编写单线程代码,再优化
  • 仅对 CPU 密集型工作使用 @concurrent——图像处理、压缩、复杂计算
  • 为大多数单线程的应用目标启用 MainActor 推断模式
  • 在卸载前进行分析——使用 Instruments 查找实际瓶颈
  • 使用 MainActor 保护全局变量——全局/静态可变状态需要 actor 隔离
  • 使用隔离协议遵循,而不是 nonisolated 变通方法或 @Sendable 包装器
  • 逐步迁移——在构建设置中一次启用一个功能

应避免的反模式

  • 对每个 async 函数都应用 @concurrent(大多数不需要后台执行)
  • 使用 nonisolated 抑制编译器错误而不理解隔离
  • 保留旧的 DispatchQueue 模式,而 actor 提供了相同的安全性
  • 在并发相关的 Foundation Models 代码中跳过 model.availability 检查
  • 与编译器对抗——如果它报告数据竞争,代码确实存在并发问题
  • 假设所有 async 代码都在后台运行(Swift 6.2 默认:停留在调用 actor 上)

何时使用

  • 所有新的 Swift 6.2+ 项目(平易近人的并发是推荐的默认设置)
  • 将现有应用从 Swift 5.x 或 6.0/6.1 并发迁移
  • 在采用 Xcode 26 期间解决数据竞争安全的编译器错误
  • 构建以 MainActor 为中心的应用架构(大多数 UI 应用)
  • 性能优化——将特定的繁重计算卸载到后台