Swift 并发 2026:async/await、Actor 与 Sendable 严格检查
去年我把一个三年的老项目从 completionHandler 回调地狱迁到 async/await,原以为是个体力活,结果在升到 Xcode 16、把 Swift language mode 切到 6 那天炸了——编译器一口气甩出 200 多条 Sendable 警告,我盯着屏幕愣了好一会儿。最离谱的是其中一条,它指着我们一个用了三年、"从来没出过问题"的图片缓存说这里有 data race。我一开始不信,跑了几遍 Thread Sanitizer,真有。那是个低概率竞争,线上偶发的图片串号 bug 找了半年没头绪,原来就是它。
从那以后我对严格并发检查的态度变了。它确实烦,但它烦得有道理。下面这些是我自己踩过、或者帮同事填过的坑,不是文档抄来的概念。
Swift 5.5 引入 async/await,Swift 6 默认打开严格的 Sendable 检查。2026 年写现代 Swift 已经绕不开这套模型了。
关键三件套
- async/await — 让异步代码看起来像同步顺序代码。
- Actor — 保护内部状态不被数据竞争破坏的类型。
- Sendable — 编译器用来证明值能安全跨并发边界的协议。
只用 async/await 能撑一会儿,但只要出现共享可变状态,actor 和 Sendable 立刻登场——Swift 6 严格检查让它们变成必须的而不是可选的。
四个概念经常被混着用,我把它们各自的用途和我栽过的坑列一下:
| 概念 | 用途 | 我踩过的坑 |
|---|---|---|
| async/await | 把异步写成顺序代码 | 以为 await 切了线程,其实没有——重 CPU 活儿照样卡主线程 |
| Task | 进入 async 上下文 / 起独立任务 | 散落各处的 Task {} 不会被父级取消,页面都关了任务还在跑 |
| Actor | 串行化保护可变状态 | 每次跨 actor 调用都要 await,循环里频繁访问会被隐式的 hop 拖慢 |
| Sendable | 证明值能安全跨并发边界 | 用 @unchecked Sendable 一时爽,等于亲手把 race 关进黑箱 |
async/await 5 分钟入门
func loadUser(id: String) async throws -> User {
let (data, _) = try await URLSession.shared.data(from: url(for: id))
return try JSONDecoder().decode(User.self, from: data)
}
// 调用方必须在 async 上下文,或用 Task 包起来
Task {
do {
let user = try await loadUser(id: "42")
} catch {
print("failed: \(error)")
}
}
心智模型:await 是挂起点,函数可以让出控制权、之后被调度回来恢复执行。它不是线程——多个 await 可以在同一个线程上交错执行。
结构化并发 — TaskGroup
要并行十个请求然后汇总:
func loadFriends(_ ids: [String]) async throws -> [User] {
try await withThrowingTaskGroup(of: User.self) { group in
for id in ids { group.addTask { try await loadUser(id: id) } }
var users: [User] = []
for try await user in group { users.append(user) }
return users
}
}
任意一个任务抛错,整组取消——这是"结构化"的含义。没有泄漏的工作,没有孤儿回调。
Actor — 受保护的可变状态
actor Cart {
private var items: [Item] = []
func add(_ item: Item) { items.append(item) }
func snapshot() -> [Item] { items }
}
let cart = Cart()
Task {
await cart.add(.init(id: "1"))
let now = await cart.snapshot()
}
Actor 把对内部状态的访问串行化。跨 actor 调用必须 await。运行时保证不会两段代码同时改 actor 状态。
@MainActor 是个特殊 actor,把隔离规则绑到主线程——SwiftUI 视图、改 UI 的 view model 都该是 @MainActor。
Swift 6 严格 Sendable
Swift 6 默认开启完整并发检查。任何跨 await 的值,编译器都要确认它是 Sendable——也就是"跨 actor/线程共享是安全的"。
自动 Sendable 的类型:
- 字段全是 Sendable 的值类型(
struct、enum) - 标记
final+Sendable且字段全是let+ Sendable 的类 Actor本身
不是 Sendable 的:
- 可变 class —— 改成 actor 或谨慎标记
- 无约束的泛型类型 —— 加
where T: Sendable
常见迁移痛点:
// ❌ strict 模式报错
final class LegacyImageCache {
private var dict: [String: UIImage] = [:]
func get(_ k: String) -> UIImage? { dict[k] }
}
// ✅ 改成 actor
actor ImageCache {
private var dict: [String: UIImage] = [:]
func get(_ k: String) -> UIImage? { dict[k] }
}
迁移老回调 API
用 withCheckedThrowingContinuation 包:
func legacyFetch() async throws -> Data {
try await withCheckedThrowingContinuation { cont in
LegacyAPI.fetch { result in
switch result {
case .success(let data): cont.resume(returning: data)
case .failure(let err): cont.resume(throwing: err)
}
}
}
}
铁律:continuation 必须恰好resume 一次。两次会 crash,零次会让等待方永远挂起。
反模式
- 到处
Task { ... }而不是await。 非结构化 Task 让父级失去对它的控制——取消不传播,错误被吞掉。 - async 代码里用
Thread.sleep。 用try await Task.sleep(nanoseconds:)。 @MainActor类里跑重 CPU 循环。 把活儿挪出主 actor,只有结果回到主 actor。- 关掉 Sendable warning 让编译通过。 你在掩盖真实的并发 bug,修它。
一句话总结
- 顺序异步用
async/await;老回调用 continuation 包。 - 并行收集用
TaskGroup,别for { Task { ... } }自求多福。 - 共享可变状态用 actor;UI 用
@MainActor。 - 相信 Swift 6 Sendable 检查——它在抓真 bug。
说句实在的,严格并发检查一开始是真烦,迁移那两周我天天在跟编译器吵架。但回头看,它拦下来的那些 data race,每一个都是能在线上炸的——前面说的图片串号 bug 就是活生生的例子,那种偶发问题你靠测试基本逮不到。所以我现在的态度很简单:别用 @unchecked Sendable 糊弄编译器。它不是在为难你,unchecked 那一刻你不是解决了问题,你只是把问题藏起来了,等它哪天在用户手机上以最难复现的方式爆出来。
如果你也正在迁老项目,建议一个 target 一个 target 地切 language mode,别一次全开,不然警告刷屏你会直接想 revert。慢慢来,反正它跑不掉。
我们是一支开发者小团队,负责构建和维护 ToolsForge —— 150+ 个完全在浏览器本地运行、隐私优先的工具。这些文章来自我们日常的工程实践,而不是纸上谈兵。
了解我们相关阅读
Objective-C 与 Swift 互操作 2026:什么仍然能用,什么悄悄坏了
2026 年 Objective-C ↔ Swift 互操作的诚实现状 — 桥接头文件、ABI 稳定性、Swift 6 严格并发对老 ObjC 代码的影响、混合代码库求生指南。
Go context 实战:deadline、取消传播与那些救命的模式
Go context 包用法精要 — deadline 与 timeout 的区别、正确传播取消、什么不该放进 context.Value、避免 goroutine 泄漏的模式。
一张照片能做多少事:AI 改图工具实战指南
换发型、换发色、换装、转卡通、风格迁移、室内设计、老照片修复——用一张照片走完这些 AI 改图流程,以及怎么让结果更稳、更像本人。