Actor 是什么?(一句话版)
Actor = 自带大门的房间:一次只能进一个人,进门要"等钥匙"(await)。
它存在的唯一理由:保护非 Sendable 的可变状态。
Actor vs Class:只差一个隔离域
| 维度 | Class | Actor |
|---|---|---|
| 引用语义 | ✅ | ✅ |
| 继承 | ✅ | ❌ |
| 隔离域 | ❌(谁都能同步访问) | ✅(必须 await进门) |
| 线程安全 | 手动锁/队列 | 编译器保证 |
| 同步调用 | 任意 | 外部禁止 |
把 Actor 想成"远程服务": 数据在"服务器"里,你要发请求(await)才能读写。
决策三要素:缺一不可!
只有同时满足下面 3 条,才值得上 Actor:
-
有非 Sendable 状态
(纯 Sendable 结构体/类 → 无需保护)
-
操作必须原子性
(读-改-写必须打包,不能中途被插)
-
这些原子操作
不能在现有 Actor 上完成(如 MainActor)
| 缺一条 → 用别的方式 | 原因 |
|---|---|
| 只有 ① 缺 ② | 用 Sendable+ 值类型即可 |
| 有 ①② 但能在 MainActor 做 | 直接标 @MainActor,还能同步访问 UI |
| 为了"避开主线程"而造 Actor | 反模式!用 @concurrent/ Task.detached即可 |
反例集合:这些 Actor 都"师出无名"
❌ 网络客户端 Actor
swift
actor APIClient {
// 全是 Sendable:URLSession、tokenString
func request() async -> Data { ... }
}
- 状态已 Sendable → 无需保护
- 副作用只是"不想跑主线程"→ 用
@concurrent函数即可 - 结果:人为加锁,解码都无法并发
❌ "看不懂并发报错"就套 Actor
swift
@globalActor actor MyRandomActor {
// 空状态,只为消 Sendable 警告
}
→ 永远别用 Actor 当创可贴!
先理解警告,再选工具(Sendable、@MainActor、@concurrent)。
正例:真正需要 Actor 的场景
✅ 本地非 Sendable 缓存
swift
actor ImageCache {
private var store: [URL: UIImage] = [:] // UIImage 非 Sendable
func image(for url: URL) async -> UIImage? {
if let img = store[url] { return img }
let data = try await URLSession.shared.data(from: url).0
let img = UIImage(data: data)!
store[url] = img
return img
}
}
- 状态非 Sendable
- 读-写-缓存必须原子
- MainActor 不适合(网络+解码耗时)
✅ 协议强制 Sendable
swift
protocol DataSource: Sendable {
func fetch() async -> [Item]
}
- 实现层含非 Sendable 状态 → 只能用 Actor 满足 Sendable
决策流程图
arduino
需要共享可变状态?
├─ 否 → 用 struct / class(Sendable)
├─ 是 → 状态 Sendable?
│ ├─ 是 → 用 Sendable 值类型或锁自由类
│ └─ 否 → 操作必须原子?
│ ├─ 否 → 拆成 Sendable 片段
│ └─ 是 → 能在 MainActor 完成?
│ ├─ 是 → @MainActor
│ └─ 否 → **上 Actor** ✅
口诀:
"Sendable 先,MainActor 其次,新 Actor 最后。"
同步访问红线:Actor = "远程服务"
外部调用必须异步:
swift
actor Counter {
func increment() { value += 1 }
}
// 外部
await counter.increment() // ✅
counter.increment() // ❌ 编译失败
→ 如果你无法容忍这种异步接口(例如实时音频回调),
根本不该用 Actor ------ 考虑锁、原子类或 @concurrent 函数。
常见误解速答
| 误解 | 真相 |
|---|---|
| "Actor 让并发更快" | 它更安全而非更快;异步排队可能更慢 |
| "把类改成 actor 就能消并发警告" | 治标不治本;先理解 Sendable 要求 |
| "网络层必须 actor" | 若状态 Sendable,用 @concurrent函数/任务即可 |
| "actor 里所有代码都异步" | 内部可完全同步;只有外部调用需 await |
一句话总结
"Actor 是保护'非 Sendable 可变状态'的昂贵保险箱------ 确认你真的有宝贝,且别处放不下,再把它请回家。"
记住三要素:
- 非 Sendable 状态
- 必须原子操作
- 现有 Actor 帮不上
同时满足 → 用 Actor;缺一条 → 找更简单的工具。
让 Actor 留在真正需要串行大门的地方,别把远程服务的复杂度,带进本可并行的小花园。