Actor 是什么?
Actor 是引用类型(与 class 类似),但自带隔离域:
- 任意时刻只允许一个任务进入 Actor 内部
- 外部调用必须加
await
,自动排队 - 编译器保证无数据竞争,无需手动加锁
对比:手动锁 vs Actor
swift
// ❌ 旧世界:DispatchQueue + 锁
class Counter {
private var value = 0
private let queue = DispatchQueue(label: "counter")
func increment() {
queue.sync { value += 1 }
}
}
// ✅ 新世界:Actor
actor Counter {
var value = 0
func increment() {
value += 1 // 无需锁,编译器保护
}
}
→ 代码量减半,永远不会忘记加锁。
Actor 使用范式
swift
let counter = Counter()
Task {
await counter.increment() // 必须 await
let current = await counter.value // 串行排队
print("Value \(current)")
}
规则速记:"进 Actor 先 await,出来无需 await。"
nonisolated: actor 里的"快速通道"
当方法不触碰 Actor 的任何可变状态时,可标 nonisolated
:
- 跳过排队 → 性能更高
- 无需 await → 调用方更轻松
- 但不能读/写任何属性(除非属性也是
nonisolated
)
示例
swift
actor Logger {
nonisolated func appName() -> String {
"MyCoolApp" // 只返回常量,无状态访问
}
func log(_ msg: String) {
print("[\(Date())] \(msg)") // 访问状态,需 await
}
}
Task {
print(Logger().appName()) // ✅ 同步调用
await Logger().log("Hello") // ✅ 需 await
}
isolated:把"外部函数"拉进 actor 内部
场景:在 extension 或工具方法里,想同步访问 actor 状态,又不想 await
。
做法:把 actor 自身作为参数标 isolated
swift
actor StatsTracker {
var total = 0
var count = 0
func showAverage() {
// 身处 actor 内部,可直接调用
print(average(from: self))
}
}
extension StatsTracker {
// 外部函数,但获得"内部通行证"
func average(from tracker: isolated StatsTracker) -> Double {
Double(tracker.total) / Double(tracker.count) // ✅ 无 await
}
}
func average1(from tracker: isolated StatsTracker) -> Double {
Double(tracker.total) / Double(tracker.count) // ✅ 无 await
}
Task {
let st = StatsTracker()
// 这里调用的时候需要加await
let s = await average1(from: st)
}
调用限制:
- 只能在同一 actor 隔离域里使用
- 外部世界无法直接
await average()
→ 编译器挡住
实战:UserService 完整例子
swift
actor UserService {
private var cache: [Int: String] = [:]
// Actor 隔离:可能挂起
func fetchUser(id: Int) async -> String {
if let name = cache[id] { return name }
try? await Task.sleep(nanoseconds: 500_000_000) // 模拟网络
let name = "User\(id)"
cache[id] = name
return name
}
// 非隔离:立即返回
nonisolated func version() -> String {
"UserService v1.0"
}
}
使用侧:
swift
let service = UserService()
Task {
print(service.version()) // 同步,无 await
print(await service.fetchUser(id: 42)) // 第一次,慢
print(await service.fetchUser(id: 42)) // 第二次,缓存瞬间返回
}
三条口诀一张图
场景 | 关键词 | 是否 await | 性能 |
---|---|---|---|
进 actor 内部 | await |
✅ | 正常排队 |
不碰状态的工具 | nonisolated |
❌ | 快速通道 |
外部函数想进内部 | isolated Actor |
❌(内部用) | 零开销 |
常见编译错误速查
错误 | 原因 | 修复 |
---|---|---|
Actor-isolated property can only be referenced with 'await' | 少写 await |
加上 await |
Call to nonisolated function must not be marked with 'await' | 多了 await |
去掉 await |
'isolated' parameter can only be called from within the same actor | 外部直接调 isolated 方法 |
把逻辑移到 actor 内部或使用普通扩展 |
什么时候用 Actor: checklist
✅ 适合
- 多任务同时读/写同一可变状态(计数器、缓存、状态机)
- 需要编译期保证无数据竞争
- 与 SwiftUI、CoreData 主线程栈搭配
❌ 不适合
- 只读常量或纯函数 → 用 struct
- 跨隔离频繁大量小调用 → 队列可能成为瓶颈
- 已有无锁队列/原子方案且极致性能
一句话总结
"Actor = 自动锁 + 编译器检查;nonisolated 开快道,isolated 让外部函数进内部。"
把 Actor 当作"线程安全保险箱":
重要物品(可变状态)放进去,拿的时候await排队;
只读文件(nonisolated)走侧门,不排队;
临时工具(isolated)让内部员工用,零等待。
用好这三板斧,写并发代码就像写单线程一样轻松!