Swift 6.2 平易并发
用于采用 Swift 6.2 并发模型的模式。在该模型下,代码默认以单线程方式运行,而并发性通过显式方式引入。它在不牺牲性能的情况下消除了常见的数据竞争错误。
何时激活
- 将 Swift 5.x 或 6.0/6.1 项目迁移到 Swift 6.2
- 解决数据竞争安全的编译器错误
- 设计基于 MainActor 的应用架构
- 将 CPU 密集型工作卸载到后台线程
- 在 MainActor 隔离的类型上实现协议一致性
- 在 Xcode 26 中启用“平易并发”构建设置
核心问题:隐式后台卸载
在 Swift 6.1 及更早版本中,异步函数可能会被隐式卸载到后台线程,即使在看似安全的代码中也会导致数据竞争错误:
// 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 修复了此问题:默认情况下,异步函数会停留在调用方的 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(NonisolatedNonsendingByDefault)。启用这些设置后,
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:
- 将包含类型标记为
nonisolated - 在函数上添加
@concurrent - 如果尚未异步,则添加
async - 在调用点添加
await
关键设计决策
| 决策 | 理由 |
|---|---|
| 默认单线程 | 大多数自然代码是无数据竞争的;并发是可选加入的 |
| Async 停留在调用方 Actor | 消除了导致数据竞争错误的隐式卸载 |
| 隔离一致性 | MainActor 类型可以在没有不安全变通方法的情况下遵守协议 |
@concurrent 显式选择加入 | 后台执行是故意的性能选择,而非偶然 |
| MainActor 默认推断 | 减少应用目标中繁冗的 @MainActor 注解 |
| 可选加入采用 | 非破坏性升级路径 — 递增地启用特性 |
迁移步骤
- 在 Xcode 中启用:构建设置中 Swift 编译器 > Concurrency 部分
- 在 SPM 中启用:在包清单中使用
SwiftSettingsAPI - 使用迁移工具:通过 swift.org/migration 自动更改代码
- 从 MainActor 默认值开始:为应用目标启用推断模式
- 在需要处添加
@concurrent:首先分析性能,然后卸载热点路径 - 彻底测试:数据竞争问题将变为编译时错误
最佳实践
- 从 MainActor 开始 — 先编写单线程代码,之后再进行优化
- 仅对 CPU 密集型工作使用
@concurrent— 图像处理、压缩、复杂计算 - 为主要是单线程的应用目标启用 MainActor 推断模式
- 在卸载前进行分析 — 使用 Instruments 查找实际瓶颈
- 使用 MainActor 保护全局变量 — 全局/静态可变状态需要 Actor 隔离
- 使用隔离一致性而非
nonisolated变通方法或@Sendable包装器 - 递增迁移 — 在构建设置中逐个启用特性
应避免的反模式
- 对每个异步函数应用
@concurrent(大多数不需要后台执行) - 在不理解隔离的情况下使用
nonisolated来抑制编译器错误 - 当 Actor 提供相同安全性时,保留传统的
DispatchQueue模式 - 在与并发相关的 Foundation Models 代码中跳过
model.availability检查 - 与编译器对抗 — 如果它报告了数据竞争,那么代码确实存在真实的并发问题
- 假设所有异步代码都运行在后台(Swift 6.2 默认:停留在调用方 Actor)
何时使用
- 所有新的 Swift 6.2+ 项目(平易并发展是推荐的默认值)
- 将现有应用从 Swift 5.x 或 6.0/6.1 并发模型迁移
- 在采用 Xcode 26 期间解决数据竞争安全编译器错误
- 构建以 MainActor 为中心的应用架构(大多数 UI 应用)
- 性能优化 — 将特定的重计算卸载到后台