核心区别:同步上下文 vs 异步上下文
// ❌ 错误:在同步函数中调用 async 方法
func syncFunction() {
let state = iRobotData.shared.state
await self.mapState(state) // 编译错误!
}
// 原因:普通函数不支持"等待",没有暂停点
// ✅ 正确:在异步函数中调用 async 方法
func asyncFunction() async {
let state = iRobotData.shared.state
await self.mapState(state) // 可以等待
}
关键:await 只能在支持暂停的上下文中使用,而不是任何地方都能用。
什么是 async/await?
async/await 是 Swift 的并发编程语法,让异步代码像同步代码一样直观。
例子:取外卖
传统回调方式(闭包):你在家,打电话点外卖,店员说做好了我打给你。你去干别的,电话响了再去拿。
func orderFood(completion: @escaping (Food) -> Void) {
// 异步做披萨
makePizza {
let pizza = Pizza()
completion(pizza) // 好了叫你
}
}
// 使用:回调地狱
orderFood { pizza in
eat(pizza)
}
async/await 方式:你在家,打电话点外卖,然后原地等待(不干别的),外卖到了继续。
func orderFood() async -> Food {
// 异步做披萨,但看起来像同步等待
let pizza = await makePizza()
return pizza
}
// 使用:像同步代码
let pizza = await orderFood()
eat(pizza)
关键区别
| 对比 | 传统回调 | async/await |
|---|---|---|
| 代码写法 | 嵌套 | 线性,像同步 |
| 错误处理 | 多处处理 | 统一 try-catch |
| 可读性 | 差 | 好 |
什么是 Task?
Task 是在非异步环境中启动异步任务的方法。
类比:你想在厨房做菜,但厨房在外面
// 你在卧室(同步环境)
// 想做菜,但菜只能在厨房(异步环境)做
// ❌ 卧室不支持做菜
func bedroom() {
await cook() // 卧室不能直接调用厨房方法
}
// ✅ 叫外卖小哥(Task)去厨房做,你继续在卧室做其他事
func bedroom() {
Task {
await cook() // 外卖小哥去厨房做
}
// 你继续玩手机
}
Task 的作用:在同步环境中,创建一个异步任务,并立即返回(不等待结果)。
为什么 withObservationTracking 的闭包不支持 await?
withObservationTracking 的 apply 闭包设计为同步,因为它需要立即读取值。
// withObservationTracking 的定义
public func withObservationTracking<T>(
_ apply: () -> T, // ← 闭包不是 async
onChange: ...
) -> T
因为它是同步闭包,所以内部不能用 await。
解决方案:用 Task 包一层
withObservationTracking {
let state = iRobotData.shared.state // 同步读取
// ✅ 创建 Task,在里面异步处理
Task {
await self.mapState(state) // 异步处理
}
} onChange: { ... }
执行流程:
- 立即读取
state(同步) - 创建一个
Task(不等待,立即返回) -
Task在后台异步执行mapState -
apply闭包继续执行其他代码(如果没有,就结束)
对比:同步 vs 异步 vs Task
1. 同步函数中调用同步方法
func syncFunc() {
let result = syncMethod() // ✅ 直接调用,等待结果
print(result)
}
2. 异步函数中调用异步方法
func asyncFunc() async {
let result = await asyncMethod() // ✅ 用 await 等待结果
print(result)
}
3. 异步函数中调用异步方法(不等待结果)
func asyncFunc() async {
Task {
await asyncMethod() // ✅ 不等待,立即返回
}
print("立即执行") // 先打印这个
// asyncMethod 在后台执行
}
4. 同步函数中调用异步方法
func syncFunc() {
await asyncMethod() // ❌ 编译错误!同步函数不能等待
Task {
await asyncMethod() // ✅ 创建 Task,不等待
}
}
你的例子重新说明
错误写法 ❌
withObservationTracking { // 同步闭包
let state = iRobotData.shared.state
await self.mapState(state) // ❌ 编译错误
// 原因:同步闭包不能用 await
} onChange: { ... }
正确写法 ✅
withObservationTracking { // 同步闭包
let state = iRobotData.shared.state
Task { // 创建异步任务
await self.mapState(state) // ✅ 在 Task 里可以用 await
}
// 闭包立即结束,Task 在后台慢慢执行
} onChange: { ... }
异步函数中调用同步函数
完全可以,而且不需要任何特殊处理。
// 同步函数
func syncMethod() -> String {
return "同步结果"
}
// 异步函数
func asyncMethod() async -> String {
// ✅ 直接调用同步函数,不需要 await
let result = syncMethod()
return result
}
为什么可以?
| 调用方向 | 是否需要 await | 原因 |
|---|---|---|
| 异步函数 → 同步函数 | ❌ 不需要 | 同步函数立即返回,不会阻塞,直接调用即可 |
| 同步函数 → 异步函数 | ✅ 需要 Task | 同步函数不能暂停,必须用 Task 包装 |
图解
┌─────────────────────────────────────────────────────────────┐
│ 异步函数 (async) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 可以直接调用同步函数 ✅ │ │
│ │ let a = syncMethod() ← 立即返回 │ │
│ │ │ │
│ │ 调用异步函数需要用 await ⚠️ │ │
│ │ let b = await asyncMethod() ← 可能等待 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 同步函数 (非 async) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 可以直接调用同步函数 ✅ │ │
│ │ let a = syncMethod() │ │
│ │ │ │
│ │ 调用异步函数需要用 Task { } ❌ │ │
│ │ Task { let b = await asyncMethod() } │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
总结表格
| 场景 | 写法 | 是否需要特殊处理 |
|---|---|---|
| 同步 → 同步 | syncMethod() |
❌ 不需要 |
| 同步 → 异步 | Task { await asyncMethod() } |
✅ 需要 Task |
| 异步 → 同步 | syncMethod() |
❌ 不需要 |
| 异步 → 异步 | await asyncMethod() |
✅ 需要 await |
一句话记住
异步函数可以"向下"调用同步函数(直接调),同步函数不能"向上"调用异步函数(需要 Task)。
什么时候用 Task?
| 场景 | 是否用 Task |
|---|---|
在 async 函数中调用 async 方法,想等待结果 |
❌ 不用,直接用 await
|
在 async 函数中调用 async 方法,不想等待 |
✅ 用 Task { }
|
在同步函数中调用 async 方法 |
✅ 用 Task { }
|
在闭包(不支持 async)中调用 async 方法 |
✅ 用 Task { }
|
总结
| 关键词 | 含义 |
|---|---|
async |
标记函数是异步的,可能内部会等待 |
await |
等待异步函数完成,只能在 async 函数中使用 |
Task |
在同步环境中启动异步任务,不等待结果 |
一句话:await 就像"暂停等待",只能在支持暂停的地方用;Task 是"派发任务",可以在任何地方用,不等待结果。