1️⃣ 核心结论
非 Actor 上下文访问 Actor 属性必须使用
await,是因为跨 Actor 调用会被封装为异步任务,放入 Actor 的 executor 队列排队执行,从而保证数据隔离和线程安全。
换句话说:await = 等待 Actor 内部串行任务完成。
2️⃣ 原理:Actor 的隔离机制
- 每个 Actor 拥有自己的 executor(串行队列)。
- Actor 内部的状态只能在这个 executor 上安全访问。
- 同时只能有一个任务在 Actor 内部执行。
Actor (executor)
├─ Task 1
├─ Task 2 queued
└─ Task 3 queued
- 任何跨 Actor 的访问都需要经过 executor 排队。
3️⃣ 编译器约束
- 如果你在 非 Actor 上下文直接访问属性,编译器报错:
actor BankAccount {
var balance: Int = 0
}
let account = BankAccount()
account.balance += 10 // ❌ 编译错误
原因:
- 当前上下文不在
BankAccount的 executor - 无法保证线程安全
- 编译器要求 跨 Actor 访问必须显式 await,以触发异步排队。
4️⃣ runtime 工作机制
当你写:
await account.deposit(100)
发生了什么:
- 编译器将调用封装成 任务(continuation)
- 这个任务被放入
account的 executor 队列 - executor 串行执行 Actor 内部代码
- await 等待任务完成,然后返回结果给调用者
所以 await 本质上是:
- 一个“切换到 Actor 的串行队列执行任务,并等待完成”的操作。
非 await 调用无法保证安全
- 如果没有 await,访问可能在不同线程同时进行
- Actor 内部属性可能被并发修改 → 数据竞争
- Swift 编译器通过类型系统和 async/await 强制你遵守
5️⃣ 作用总结
| 功能 | 解释 |
|---|---|
await | 等待 Actor 内部任务执行完成 |
| executor | 串行化 Actor 内部访问 |
| async 消息 | 跨 Actor 访问封装为任务 |
| 数据隔离 | 保证同一时间只有一个任务访问属性 |
6️⃣ 类比理解
- Actor = 单线程服务 + 异步队列
- 非 Actor 调用 = 发消息请求
- await = 等待消息处理完成返回结果
像发快递:
- 你不能直接拿仓库里的东西(非 Actor 上下文访问)
- 只能发快递(async/await),等待仓库处理完成才拿到
7️⃣ 面试必背总结
在 Swift 中,非 Actor 上下文访问 Actor 属性必须使用
await,因为每个 Actor 内部有自己的 executor,属性访问必须在该 executor 上串行执行;await会把调用封装成异步任务,放入 Actor 队列执行并等待完成,从而保证数据隔离和线程安全。