本文目录导读:

- 案例 1:基础用法(并行请求合并)
- 案例 2:等待两个异步网络请求(Combine 标准写法)
- 案例 3:RxSwift 中的 thenCombine(如果你需要对比)
- 核心总结(面试重点)
- 延伸:与
zip的区别(高频考点)
在 Swift 并发编程中,thenCombine 是 Combine 框架中的一个核心操作符,它的作用是将两个 Publisher 的最新值组合成一个元组,当两个 Publisher 都发出新值时,才会触发下游。
这在处理多个独立异步请求(如并行加载用户信息和用户头像)时非常有用。
以下是几个由浅入深的案例:
案例 1:基础用法(并行请求合并)
场景:同时请求“用户信息”和“用户设置”,都成功后,将结果整合展示。
import Combine
import Foundation
// 模拟网络请求1:获取用户信息
func fetchUserInfo() -> AnyPublisher<String, Never> {
return Future<String, Never> { promise in
DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
promise(.success("张三,年龄28"))
}
}
.eraseToAnyPublisher()
}
// 模拟网络请求2:获取用户设置
func fetchUserSettings() -> AnyPublisher<String, Never> {
return Future<String, Never> { promise in
DispatchQueue.global().asyncAfter(deadline: .now() + 1.5) {
promise(.success("深色模式,语言:中文"))
}
}
.eraseToAnyPublisher()
}
// 核心代码:
func loadUserProfile() {
// 1. 发起两个异步请求
let publisher1 = fetchUserInfo()
let publisher2 = fetchUserSettings()
// 2. 使用 thenCombine 合并
let cancellable = publisher1
.combineLatest(publisher2) // 错误写法?不,下面是正确写法
.sink { info, settings in
print("合并结果:\(info) —— \(settings)")
}
// 注意:Combine 中正确写法是 combineLatest,但面试常问 thenCombine 在 RxSwift 中的对应。
// 在 Combine 中,对应的操作符就是 combineLatest。
}
重要澄清:在 Swift Combine 中,并没有名为 thenCombine 的操作符。Combine 使用的是 combineLatest,如果你之前用过 RxSwift,thenCombine 是 RxSwift 的操作符,在 Combine 中,职责相同的是 combineLatest。
下面是 Combine 的实际写法:
import Combine
let pub1 = Just("Hello")
let pub2 = Just("World")
let cancellable = pub1
.combineLatest(pub2)
.sink { value1, value2 in
print("\(value1) \(value2)") // 输出:Hello World
}
案例 2:等待两个异步网络请求(Combine 标准写法)
这个是完整的实际业务场景,使用了 URLSession 和 Future。
import Combine
import UIKit
// 模拟两个不同的 API 端点
enum API {
static func requestUser() -> AnyPublisher<UserModel, Error> {
let url = URL(string: "https://api.example.com/user")!
return URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: UserModel.self, decoder: JSONDecoder())
.eraseToAnyPublisher()
}
static func requestOrders() -> AnyPublisher<[OrderModel], Error> {
let url = URL(string: "https://api.example.com/orders")!
return URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: [OrderModel].self, decoder: JSONDecoder())
.eraseToAnyPublisher()
}
}
// 视图模型中使用
class ProfileViewModel: ObservableObject {
@Published var user: UserModel?
@Published var orders: [OrderModel] = []
@Published var isLoading = false
private var cancellables = Set<AnyCancellable>()
func loadData() {
isLoading = true
// 关键:同时请求,等待两者都完成再处理
API.requestUser()
.combineLatest(API.requestOrders()) // 相当于 RxSwift 的 thenCombine
.sink { completion in
self.isLoading = false
switch completion {
case .finished: break
case .failure(let error):
print("加载失败: \(error)")
}
} receiveValue: { user, orders in
self.user = user
self.orders = orders
print("数据全部加载完成:\(user.name) 有 \(orders.count) 个订单")
}
.store(in: &cancellables)
}
}
案例 3:RxSwift 中的 thenCombine(如果你需要对比)
如果你面试或维护的旧项目使用的是 RxSwift,那么代码长这样:
import RxSwift
import RxCocoa
let disposeBag = DisposeBag()
// 两个 Observable
let username = BehaviorSubject<String>(value: "尚未输入") // 模拟用户名输入
let passwordLength = BehaviorSubject<Int>(value: 0) // 模拟密码长度
// thenCombine 的用法
Observable
.combineLatest(username, passwordLength) // 注意:这里就是 combineLatest
.map { name, length in
return "用户名是 \(name),密码长度为 \(length)"
}
.subscribe(onNext: { text in
print(text)
})
.disposed(by: disposeBag)
// 模拟输入变化
username.onNext("John")
passwordLength.onNext(8)
// 输出:用户名是 John,密码长度为 8
核心总结(面试重点)
| 特性 | Combine | RxSwift |
|---|---|---|
| 操作符名称 | combineLatest |
combineLatest 或 thenCombine(用于指定 pair) |
| 作用 | 两个 Publisher 都发出新值时,合并成一个元组 | 同左 |
| 触发时机 | 只要其中任何一个 Publisher 发出新值,下游就会触发一次 | 同左 |
| 计数 | 总是返回最新的两个值 | 同左 |
注意陷阱:
combineLatest会等待两个 Publisher 都发出第一个值后,才会第一次触发下游。- 如果其中一个 Publisher 发出多个值,它只保留最新的值,直到另一个也发出新值,才组合。
- 错误处理:如果其中任何一个 Publisher 发出错误(
Failure),整个CombineLatest会立即终止并发送错误。
延伸:与 zip 的区别(高频考点)
你可能会被追问:“combineLatest 和 zip 有什么区别?”
combineLatest:每次任意一方发出新值,就立即取当前双方的最新值合并,适合“天气温度和湿度同时变化”的场景。zip:严格配对,双方必须按顺序一一对应,当 A 发出第 1 个值,B 也发出第 1 个值,才组合;然后等待 A 第 2 个值和 B 第 2 个值,如果一方发了很多,另一方没发,就不会触发,适合“下载文件和写日志同时完成”的场景。