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: 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 }
// Error: Sending 'self.photoProcessor' risks causing data races
return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
}
}
在 Swift 6.2 中,该问题已修复:异步函数默认停留在调用方所在的 Actor 上。
// Swift 6.2: OK — async stays on MainActor, no data race
@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: ERROR — crosses into main actor-isolated code
// Swift 6.2: OK with isolated conformance
extension StickerModel: @MainActor Exportable {
func export() {
photoProcessor.exportAsPNG()
}
}
编译器会保证该一致性仅在 MainActor 上使用:
// OK — ImageExporter is also @MainActor
@MainActor
struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // Safe: same actor isolation
}
}
// ERROR — nonisolated context can't use MainActor conformance
nonisolated struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // Error: Main actor-isolated conformance cannot be used here
}
}
核心模式——全局变量和静态变量¶
使用 MainActor 保护全局/静态状态:
// Swift 6.1: ERROR — non-Sendable type may have shared mutable state
final class StickerLibrary {
static let shared: StickerLibrary = .init() // Error
}
// Fix: Annotate with @MainActor
@MainActor
final class StickerLibrary {
static let shared: StickerLibrary = .init() // OK
}
MainActor 默认推断模式¶
在 Swift 6.2 中引入了默认推断 MainActor 的模式——无需手动注解:
// 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()
}
}
该模式为可选启用,推荐用于应用、脚本和其他可执行目标。
核心模式——使用 @concurrent 的后台处理¶
当需要真正的并行处理时,使用 @concurrent 显式卸载:
重要: 此示例需要启用“可访问并发”构建设置——SE-0466(MainActor 默认隔离)和 SE-0461(默认非隔离非 Sendable)。启用这些设置后,
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
}
// Offload expensive work to concurrent thread pool
@concurrent
static func extractSubject(from data: Data) async -> Sticker { /* ... */ }
}
// Callers must await
let processor = PhotoProcessor()
processedPhotos[item.id] = await processor.extractSticker(data: data, with: item.id)
使用 @concurrent 需要:
- 将容器类型标记为
nonisolated - 为函数添加
@concurrent - 如果函数还不是异步的,则添加
async - 在调用方添加
await
重要的设计决策¶
| 决策 | 理由 |
|---|---|
| 默认单线程 | 最自然的代码没有数据竞争。并发是可选启用 |
| 异步函数停留在调用方所在的 Actor 上 | 排除引发数据竞争错误的隐式卸载 |
| 隔离一致性 | MainActor 类型无需不安全变通方案即可遵循协议 |
通过 @concurrent 显式可选启用 |
后台执行不是偶然的,而是有意的性能选择 |
| MainActor 默认推断 | 减少应用目标中典型的 @MainActor 注解 |
| 采用可选启用 | 非破坏性迁移路径——逐步启用功能 |
迁移步骤¶
- 在 Xcode 中启用:构建设置中的 Swift Compiler > 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 应用)
- 性能优化——将特定繁重计算卸载到后台时
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 ev/ECC