Combine 是 Apple 推出的一个框架,用于处理异步事件和响应式编程。它可以帮助开发者以声明性的方式处理复杂的异步任务,例如网络请求、定时器、UI 更新等。
核心概念
1、Publisher(发布者)
提供数据流,可以是单个值或多个值。
代表事件的来源,比如网络请求结果、用户输入、定时器触发等。
常见的内置 Publisher:
1)Just:发布一个单一值。
Just 是一种特殊的 Publisher,它只发布一个单一的值,然后立即完成。
适用于需要立即返回一个简单值的场景。
特点:
只能发布一个值。
一旦创建,就不可修改。
发布后立即完成订阅。
import Combine
let justPublisher = Just(42)
let subscription = justPublisher.sink(
receiveCompletion: { completion in
print("Completion: \(completion)") // 打印 "Completion: finished"
},
receiveValue: { value in
print("Value: \(value)") // 打印 "Value: 42"
}
)
// 输出:
// Value: 42
// Completion: finished
2)PassthroughSubject:动态发布多个值。
它不会保留任何先前发送的值,新的订阅者只会收到订阅之后的值。
常用于处理用户交互、通知或其他需要即时传播的事件。
特点:
没有初始值。
只会发布订阅后产生的值。
手动调用 .send(_:) 发布数据。
import Combine
let subject = PassthroughSubject<String, Never>()
let subscription = subject.sink(
receiveCompletion: { completion in
print("Completion: \(completion)")
},
receiveValue: { value in
print("Value: \(value)")
}
)
subject.send("Hello") // 打印 "Value: Hello"
subject.send("World") // 打印 "Value: World"
subject.send(completion: .finished) // 打印 "Completion: finished"
3)CurrentValueSubject:持有一个当前值,并可以发布新的值。
每次订阅时,订阅者都会收到当前值,然后再接收之后的值。
常用于状态管理或需要持有某个值的场景。
特点:
持有一个初始值。
任何订阅者都会先收到当前值。
手动调用 .send(_:) 更新值。
import Combine
let currentValueSubject = CurrentValueSubject<Int, Never>(10)
let subscription1 = currentValueSubject.sink(
receiveCompletion: { completion in
print("Completion: \(completion)")
},
receiveValue: { value in
print("Subscription1 Value: \(value)")
}
)
currentValueSubject.send(20) // Subscription1 收到 20
let subscription2 = currentValueSubject.sink(
receiveValue: { value in
print("Subscription2 Value: \(value)")
}
)
// Subscription2 会先收到当前值 20
currentValueSubject.send(30) // 两个订阅者都收到 30
// 输出:
// Subscription1 Value: 10
// Subscription1 Value: 20
// Subscription2 Value: 20
// Subscription1 Value: 30
// Subscription2 Value: 30
2、Subscriber(订阅者)
订阅 Publisher,接收其发布的数据。
开发者可以实现自己的订阅者,或者使用 Combine 提供的内置方法,例如 .sink 或 .assign。
3、Operator(操作符)
用于对数据流进行变换、过滤、合并等操作。
常用操作符:
map:将一个值映射为另一个值。
filter:筛选满足条件的数据。
combineLatest:合并多个 Publisher 的最新值。
flatMap:将一个 Publisher 的值映射为另一个 Publisher。
4、Cancellable(取消任务)
订阅后返回的对象,可以通过调用 .cancel() 停止订阅,避免内存泄漏。
5、Subjects(主题)
既是 Publisher,也是 Subscriber。
可用于手动触发值的发布。
PassthroughSubject:初始没有值,仅发送接收到的值。
CurrentValueSubject:保存当前值,并向新订阅者发送最新值。
简单示例:数据流处理
import Combine
// 1. 创建一个 Publisher
let numbers = [1, 2, 3, 4, 5].publisher
// 2. 使用 Operator 转换数据
let subscription = numbers
.filter { $0 % 2 == 0 } // 只保留偶数
.map { $0 * 10 } // 将每个值乘以 10
.sink { value in
print("Received value: \(value)")
}
// 输出:
// Received value: 20
// Received value: 40
与 SwiftUI 集成:@Published 和 @StateObject
在 MVVM 架构中,Combine 常用于连接 ViewModel 和 View。SwiftUI 提供了便捷的 @Published 和 @StateObject,简化了响应式编程。
示例:ViewModel 与 View 的绑定
import SwiftUI
import Combine
// ViewModel
class CounterViewModel: ObservableObject {
@Published var count: Int = 0
func increment() {
count += 1
}
}
// View
struct CounterView: View {
@StateObject private var viewModel = CounterViewModel()
var body: some View {
VStack {
Text("Count: \(viewModel.count)")
.font(.largeTitle)
Button("Increment") {
viewModel.increment()
}
.padding()
}
}
}
高级用法:处理网络请求
通过 Combine,可以优雅地处理网络请求并将数据绑定到 UI。
示例:网络请求 + SwiftUI
import SwiftUI
import Combine
class APIViewModel: ObservableObject {
@Published var data: String = "Loading..."
private var cancellable: AnyCancellable?
func fetchData() {
let url = URL(string: "https://api.example.com/data")!
cancellable = URLSession.shared.dataTaskPublisher(for: url)
.map { String(data: $0.data, encoding: .utf8) ?? "No Data" }
.replaceError(with: "Error fetching data")
.receive(on: DispatchQueue.main)
.sink { [weak self] in self?.data = $0 }
}
}
struct APIView: View {
@StateObject private var viewModel = APIViewModel()
var body: some View {
VStack {
Text(viewModel.data)
.padding()
Button("Fetch Data") {
viewModel.fetchData()
}
}
.onAppear {
viewModel.fetchData()
}
}
}
优点
1、声明式编程:代码可读性高,逻辑清晰。
2、易于组合:通过操作符轻松处理数据流的复杂逻辑。
3、线程管理:通过 .receive(on:) 控制数据在哪个线程中处理。