Swift 6.2 并发模型:async 函数不再隐式跳线程,@concurrent 才是明确授权
把一个 Swift 项目从 6.1 升级到 6.2(Xcode 26)时,我遇到了一批编译错误集体消失——但同时发现原来依赖"隐式后台执行"的代码逻辑悄悄变了。起初以为是编译器 bug,细查之后才意识到这是一次设计层面的语义翻转。
现象
Swift 6.1 下,下面这段代码会报数据竞争错误:
// Swift 6.1: ERROR — Sending 'self.photoProcessor' risks causing data races
@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)
}
}错误来源:photoProcessor 是 @MainActor 隔离的,但 async 函数在 Swift 6.1 中可以被隐式 offload 到后台线程,编译器判定存在跨 actor 的数据竞争风险。
升级到 Swift 6.2 后,同一段代码编译通过,不需要任何改动。
根因
Swift 6.2 引入了 Approachable Concurrency(SE-0461 NonisolatedNonsendingByDefault):
- Swift 6.1 及之前:
async函数默认可被运行时隐式调度到任意后台线程,调用方 actor 的隔离上下文并不传递。这是"性能优先"的设计,但代价是编写@MainActor代码时不断触雷。 - Swift 6.2:
async函数默认继承调用方的 actor 隔离——在@MainActor函数里await一个async方法,该方法仍运行在主线程上,不会跳出去。
在 Swift 6.2 下,await someHeavyAsyncFunc() 不会自动把工作扔到后台线程。如果你的 async 函数里有 CPU 密集型计算,且调用方是 @MainActor,那段计算仍在主线程执行,会卡 UI。
正解
需要后台并行执行时,用 @concurrent 显式标注:
nonisolated final class PhotoProcessor {
private var cachedStickers: [String: Sticker] = [:]
// 普通 async:继承调用方 actor,不跳线程
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:明确要求在后台并发线程池执行
@concurrent
static func extractSubject(from data: Data) async -> Sticker { /* 耗时图像处理 */ }
}使用 @concurrent 有四个前提:
- 含类型需标注
nonisolated(避免 actor 隔离冲突) - 函数加
@concurrent - 函数必须是
async - 调用侧加
await
另一个高频改动是 isolated conformance——@MainActor 类型实现非隔离协议时,Swift 6.2 允许在 extension 上标注 actor:
// Swift 6.1: 无法在 MainActor 类型上安全实现非隔离协议
// Swift 6.2: OK
extension StickerModel: @MainActor Exportable {
func export() {
photoProcessor.exportAsPNG()
}
}编译器会确保这个 conformance 只在 @MainActor 上下文中被使用——在 nonisolated 的代码里把 StickerModel 加入 [any Exportable] 数组依然是编译错误。
Swift 6.2 的心智模型是单线程优先:默认写 @MainActor 代码,数据竞争在编译期消除;只有经 Instruments 确认是 CPU 热点的函数,才加 @concurrent 显式放入后台线程池。
Xcode 26 的 Build Settings 中找到 Swift Compiler > Concurrency,可逐项开启 SE-0461(NonisolatedNonsendingByDefault)和 SE-0466(MainActor default isolation)。两个开关独立,可以分批启用,不是一刀切的 breaking change。
一句话外卖
Swift 6.2 之后,
await不再是"后台执行"的语义糖;想要后台,必须明写@concurrent——这一字之差,决定了你的 UI 是否会因并发推断错误而卡顿或崩溃。