Combine 是 Apple 的响应式编程框架,用于处理异步事件流。本教程涵盖 Combine 的核心概念、常用操作符、内存管理、错误处理以及与 SwiftUI 的集成,适合初学者和进阶开发者。
一、核心概念
1. Publisher:数据发送者
Publisher 发出值(Output)或错误(Failure)。常见实现:
-
Just:发出单一值后完成。let publisher = Just("Hello") // 发出 "Hello" 后结束 -
Future:异步执行并发出结果。let future = Future<String, Error> { promise in DispatchQueue.global().async { promise(.success("Result")) // 异步成功结果 } } -
Timer.publish:定时发出事件。Timer.publish(every: 1.0, on: .main, in: .common) // 每秒发出时间 -
PassthroughSubject:手动发送值,无初始值。let subject = PassthroughSubject<String, Never>() subject.send("Event") // 手动触发事件 -
CurrentValueSubject:带当前值的 Publisher。let currentValue = CurrentValueSubject<String, Never>("Initial") currentValue.value = "New" // 更新值并发送
2. Subscriber:数据接收者
Subscriber 处理接收到的值或完成事件。
-
sink:处理值和完成事件。publisher.sink( receiveCompletion: { completion in if case .failure(let error) = completion { print("Error: \(error)") } }, receiveValue: { value in print("Value: \(value)") } ) -
assign(to:on:):绑定值到对象的属性(需为class类型)。class ViewModel { @Published var text = "" } let vm = ViewModel() publisher.assign(to: \.text, on: vm) // 绑定到 @Published 属性
二、常用操作符
1. 数据转换
-
.map:转换每个值。// 将输入值加倍 [1, 2, 3].publisher.map { $0 * 2 } // 输出: 2, 4, 6 -
.flatMap:将值转换为新 Publisher 并合并结果。// 每个字母追加 "1" ["A", "B"].publisher.flatMap { Just($0 + "1") } // 输出: "A1", "B1" -
.scan:累积计算。// 计算累计和 [1, 2, 3].publisher.scan(0, +) // 输出: 1, 3, 6
2. 数据过滤
-
.filter:过滤符合条件的值。// 过滤大于 1 的值 [1, 2, 3].publisher.filter { $0 > 1 } // 输出: 2, 3 -
.removeDuplicates:去除重复值。// 去除重复字母 ["A", "A", "B"].publisher.removeDuplicates() // 输出: "A", "B"
3. 时间处理
-
.debounce:防抖,延迟处理。// 500ms 内无新输入才处理 $searchText.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main) -
.throttle:节流,固定间隔处理。// 每秒处理最新值 $buttonTaps.throttle(for: .seconds(1), scheduler: DispatchQueue.main, latest: true)
4. 合并流
-
.merge(with:):合并多个 Publisher 的输出。// 合并两个序列 let p1 = [1, 2].publisher let p2 = [3, 4].publisher p1.merge(with: p2) // 输出: 1, 2, 3, 4 -
.combineLatest:合并最新值。// 组合最新值 let p1 = PassthroughSubject<Int, Never>() let p2 = PassthroughSubject<String, Never>() p1.combineLatest(p2) // 输出: (1, "A"), (2, "A"), (2, "B") p1.send(1); p2.send("A"); p1.send(2); p2.send("B")
5. 序列操作
-
.first:取第一个符合条件的值。// 取第一个大于 2 的值 [1, 2, 3, 4].publisher.first(where: { $0 > 2 }) // 输出: 3
三、错误处理
-
.catch:捕获错误并替换为新 Publisher。// 错误时返回默认值 publisher.catch { _ in Just("Fallback") } -
.replaceError:用默认值替换错误。// 错误时返回 "Default" publisher.replaceError(with: "Default") -
.retry:重试失败的 Publisher。// 最多重试 3 次 publisher.retry(3)
四、内存管理
-
AnyCancellable:管理订阅生命周期。private var cancellables = Set<AnyCancellable>() publisher.sink { _ in }.store(in: &cancellables) // 存储订阅 - 手动取消:
let cancellable = publisher.sink { _ in } cancellable.cancel() // 手动取消
五、调度与多线程
-
receive(on:):指定接收值的线程。// 在主线程接收值 publisher.receive(on: DispatchQueue.main) .sink { value in print("Received: \(value)") } .store(in: &cancellables) -
subscribe(on:):指定订阅执行的线程。// 在后台线程订阅 publisher.subscribe(on: DispatchQueue.global()) .sink { _ in } .store(in: &cancellables)
六、与 SwiftUI 集成
1. @Published 和 ObservableObject
自动更新 SwiftUI 视图。
class LoginViewModel: ObservableObject {
@Published var username = ""
@Published var password = ""
@Published var isLoginEnabled = false
private var cancellables = Set<AnyCancellable>()
init() {
// 验证用户名和密码是否有效
Publishers.CombineLatest($username, $password)
.map { username, password in
!username.isEmpty && password.count >= 6
}
.assign(to: &$isLoginEnabled)
}
}
2. SwiftUI 视图
struct LoginView: View {
@StateObject private var viewModel = LoginViewModel()
var body: some View {
Form {
TextField("Username", text: $viewModel.username)
SecureField("Password", text: $viewModel.password)
Button("Login", action: { print("Logging in...") })
.disabled(!viewModel.isLoginEnabled)
}
}
}
七、调试工具
-
print:打印事件流,方便调试。// 打印所有事件 publisher.print("Debug").sink { _ in }.store(in: &cancellables) -
handleEvents:添加副作用(如日志)。publisher.handleEvents(receiveOutput: { print("Value: \($0)") })
八、常见应用场景
1. 网络请求
func fetchData() -> AnyPublisher<Data, Error> {
URLSession.shared.dataTaskPublisher(for: URL(string: "https://api.example.com")!)
.map(\.data)
.mapError { $0 }
.eraseToAnyPublisher()
}
2. 防抖搜索
$searchText
.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
.removeDuplicates()
.flatMap { query in
search(query).replaceError(with: []) // 错误时返回空数组
}
.assign(to: \.results, on: self)
总结
| 类别 | 核心 API | 用途 |
|---|---|---|
| Publisher |
Just, Future, PassthroughSubject
|
创建数据流 |
| Subscriber |
sink, assign
|
接收数据 |
| 操作符 |
map, filter, debounce, combineLatest
|
处理数据流 |
| 错误处理 |
catch, retry, replaceError
|
错误恢复 |
| 内存管理 |
AnyCancellable, store(in:)
|
避免内存泄漏 |
| SwiftUI |
@Published, onReceive, ObservableObject
|
视图绑定 |
Combine 提供强大的异步事件处理能力,结合 SwiftUI 可实现响应式 UI 更新。通过合理使用操作符、调度和错误处理,开发者能高效构建健壮的应用。