在 Swift 现代开发中,Combine 和 Swift Concurrency (async/await) 并非互斥关系,而是互补关系。
Combine 擅长处理连续的、高频的、可操作的事件流(如 UI 交互、传感器数据);而 async/await 擅长处理一次性的、线性的异步任务(如单个网络请求、文件 IO)。
以下是它们共存并协作的四个核心维度:
1. 将 Combine 流转换为 AsyncSequence
如果你有一个现成的 Combine Publisher,但想在异步环境(如 Task)中使用 for-in 循环来消费它,可以使用 .values 属性。
- 原理:
.values将 Publisher 包装成一个遵循AsyncSequence协议的对象。 - 场景:在
Task中顺序处理通知、监听状态变化。
Swift
let publisher = NotificationCenter.default.publisher(for: .userDidLogin)
Task {
// 使用 Swift Concurrency 语法消费 Combine 流
for await notification in publisher.values {
print("用户已登录: (notification)")
}
}
2. 在 Combine 链中调用异步函数
这是最常见的共存方式。当你需要在 Combine 的操作符(如 map)里执行一个 async 函数时,可以利用 flatMap 配合 Future 或直接在闭包中使用异步逻辑。
- 最佳实践:使用
map或flatMap来触发Task。
Swift
searchSubject
.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
.flatMap { query in
// 将 async 函数包装进 Future
Future { promise in
Task {
let result = await performAsyncSearch(query)
promise(.success(result))
}
}
}
.assign(to: &$results)
3. 将异步函数封装为 Publisher
如果你正在从旧的 Combine 架构向 async/await 迁移,或者你希望利用 Combine 的强大操作符(如 retry, zip)来处理一个异步任务,你可以将 async 函数封装为 Future。
- 核心逻辑:
Future是连接这两个世界的桥梁,它能捕获异步任务的单次执行结果。
Swift
func fetchDataPublisher() -> AnyPublisher<Data, Error> {
Future { promise in
Task {
do {
let (data, _) = try await URLSession.shared.data(from: url)
promise(.success(data))
} catch {
promise(.failure(error))
}
}
}
.eraseToAnyPublisher()
}
4. 架构分工:谁负责什么?
在混合架构中,建议遵循以下分层原则,以避免逻辑混乱:
| 维度 | 推荐使用 Combine | 推荐使用 Async/Await |
|---|---|---|
| 数据特性 | 多值流 (持续不断的更新) | 单值/元组 (请求 -> 响应) |
| 逻辑复杂度 | 需要高阶转换 (Zip, CombineLatest) | 简单的线性逻辑、循环、条件分支 |
| 生命周期 | 需要精确的 cancel() 手控 | 依赖 Task 作用域自动取消 |
| UI 绑定 | @Published 与 SwiftUI 的深度绑定 | 配合 @MainActor 处理简单更新 |
5. 防御式避坑指南
- 背压与性能:
AsyncSequence的消费速度受限于循环体。如果 Combine 产生数据的速度极快(如高频传感器),而你的for await处理太慢,可能会导致内存中数据的积压。此时应优先在 Combine 层使用buffer或throttle。 - 线程安全:Combine 依赖
Scheduler(如DispatchQueue),而 Swift Concurrency 依赖Executor。在两者切换时,务必明确数据的执行环境。建议在更新 UI 前显式使用.receive(on: DispatchQueue.main)或将 ViewModel 标注为@MainActor。
总结
- Combine 负责“编排”:处理复杂的流控、聚合和频率限制。
- Async/Await 负责“执行”:处理具体的、具体的、易读的异步逻辑块。