返回博客

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.2async 函数默认继承调用方的 actor 隔离——在 @MainActor 函数里 await 一个 async 方法,该方法仍运行在主线程上,不会跳出去。
坑:以为 await 会自动后台执行

在 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 有四个前提:

  1. 含类型需标注 nonisolated(避免 actor 隔离冲突)
  2. 函数加 @concurrent
  3. 函数必须是 async
  4. 调用侧加 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] 数组依然是编译错误。

正解:单线程优先,@concurrent 显式授权

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 是否会因并发推断错误而卡顿或崩溃。