Swift 并发:我到底该不该用 Actor?——一张决策图帮你拍板

Actor 是什么?(一句话版)

Actor = 自带大门的房间:一次只能进一个人,进门要"等钥匙"(await)。

它存在的唯一理由:保护非 Sendable 的可变状态。

Actor vs Class:只差一个隔离域

维度 Class Actor
引用语义
继承
隔离域 ❌(谁都能同步访问) ✅(必须 await进门)
线程安全 手动锁/队列 编译器保证
同步调用 任意 外部禁止

把 Actor 想成"远程服务": 数据在"服务器"里,你要发请求(await)才能读写。

决策三要素:缺一不可!

只有同时满足下面 3 条,才值得上 Actor:

  1. 有非 Sendable 状态

    (纯 Sendable 结构体/类 → 无需保护)

  2. 操作必须原子性

    (读-改-写必须打包,不能中途被插)

  3. 这些原子操作

    不能在现有 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 可变状态'的昂贵保险箱------ 确认你真的有宝贝,且别处放不下,再把它请回家。"

记住三要素:

  1. 非 Sendable 状态
  2. 必须原子操作
  3. 现有 Actor 帮不上

同时满足 → 用 Actor;缺一条 → 找更简单的工具。

让 Actor 留在真正需要串行大门的地方,别把远程服务的复杂度,带进本可并行的小花园。

相关推荐
HarderCoder2 小时前
深入理解 DispatchQueue.sync 的死锁陷阱:原理、案例与最佳实践
swift
东坡肘子3 小时前
Skip Fuse现在对独立开发者免费! -- 肘子的 Swift 周报 #0110
android·swiftui·swift
Kapaseker21 小时前
Swift 构建 Android 应用?它来了
ios·swift
HarderCoder1 天前
Swift 协议(Protocol)指南(四):协议扩展(Protocol Extension)——让“协议”自己也有默认实现
swift
HarderCoder1 天前
Swift 协议(Protocol)指南(三):Primary Associated Type、some/any 与泛型式协议实战
swift
HarderCoder1 天前
Swift 协议(Protocol)指南(二):关联类型、Self 约束与泛型递归,一次彻底搞懂
swift
HarderCoder1 天前
Swift 协议(Protocol)指南(一):从语法到实战
swift
HarderCoder1 天前
Swift TaskGroup 结果顺序踩坑指南:为什么返回顺序和创建顺序不一致,以及最通用的修复办法
swift
Swift社区1 天前
iOS 基于 Foundation Model 构建媒体流
ios·iphone·swift·媒体