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 留在真正需要串行大门的地方,别把远程服务的复杂度,带进本可并行的小花园。

相关推荐
用户38034165882971 小时前
八个私有 SPM 包撑起六款 App:分支当版本、钻石依赖,和一次谁都没报错的失效
ios·swift
健了个平_2415 小时前
【iOS】关于传统竖屏 UIKit 项目如何简单适配 iPhone Duo 的第一版
ios·app·swift
2501_9151063221 小时前
iOS开发之Xcode常用调试技巧总结
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
sakiko_1 天前
使用SPM(Xcode27), 并解决Package.swift不存在问题
swift
Coder_张三3 天前
iOS 26 适配
前端·ios·swift
用户38034165882973 天前
崩溃率从 0.3% 到 0.04%:我们做对的不是修 bug,是让每个崩溃只发生一次
ios·swift
东坡肘子3 天前
当 Build in Public 重新转入地下 -- 肘子的 Swift 周报 #157
人工智能·swiftui·swift
用户38034165882974 天前
Flutter 迁 SwiftUI:AI 把页面"搬"过来只用了两周,我们补结构花了两个月
ios·swift
茶底世界之下4 天前
四角变换后的最小包围视口,为什么不能只把宽高取整?
ios·swift
茶底世界之下5 天前
预乘 Alpha 图层合成为什么会越叠越暗?
架构·swift