Combine 是 Apple 在 WWDC 2019 推出的一个响应式编程框架,用于处理随时间变化的事件流。它提供了一种声明式的方式来处理异步事件,非常适合处理网络请求、用户界面事件、定时器等场景。
Combine 核心概念
1. Publisher(发布者)
Publisher 是事件的来源,可以发出三种事件:
- 值(Output)
- 错误(Failure)
- 完成(Completion)
2. Subscriber(订阅者)
Subscriber 接收来自 Publisher 的事件,有三种方法:
receive(subscription:)
- 接收订阅receive(_:)
- 接收值receive(completion:)
- 接收完成或错误
3. Operator(操作符)
Operator 用于转换、过滤或组合 Publisher 发出的事件流。
基本使用示例
1. 简单 Publisher 和 Subscriber
swift
swift
import Combine
// 创建一个简单的 Publisher
let publisher = Just("Hello, Combine!")
// 创建一个 Subscriber
let subscriber = publisher.sink(
receiveCompletion: { completion in
switch completion {
case .finished:
print("Finished")
case .failure(let error):
print("Error: (error)")
}
},
receiveValue: { value in
print("Received value: (value)")
}
)
2. 序列 Publisher
swift
scss
let numbers = [1, 2, 3, 4, 5].publisher
numbers
.sink { number in
print(number)
}
3. 使用操作符
swift
swift
[1, 2, 3, 4, 5].publisher
.filter { $0 % 2 == 0 } // 只保留偶数
.map { $0 * $0 } // 平方
.sink { print($0) } // 输出: 4, 16
常用操作符示例
1. Map
swift
scss
[1, 2, 3].publisher
.map { $0 * 10 }
.sink { print($0) } // 输出: 10, 20, 30
2. Filter
swift
scss
[1, 2, 3, 4, 5].publisher
.filter { $0 > 3 }
.sink { print($0) } // 输出: 4, 5
3. Reduce
swift
javascript
[1, 2, 3, 4].publisher
.reduce(0) { $0 + $1 }
.sink { print($0) } // 输出: 10
4. CombineLatest
swift
scss
let publisher1 = PassthroughSubject<Int, Never>()
let publisher2 = PassthroughSubject<String, Never>()
publisher1
.combineLatest(publisher2)
.sink { print("CombineLatest: ($0), ($1)") }
publisher1.send(1)
publisher2.send("A") // 输出: CombineLatest: 1, A
publisher1.send(2) // 输出: CombineLatest: 2, A
publisher2.send("B") // 输出: CombineLatest: 2, B
5. Merge
swift
scss
let publisherA = PassthroughSubject<Int, Never>()
let publisherB = PassthroughSubject<Int, Never>()
publisherA
.merge(with: publisherB)
.sink { print("Merged: ($0)") }
publisherA.send(1) // 输出: Merged: 1
publisherB.send(2) // 输出: Merged: 2
publisherA.send(3) // 输出: Merged: 3
实际应用示例
1. 网络请求
swift
swift
import Combine
struct User: Decodable {
let name: String
let email: String
}
func fetchUser() -> AnyPublisher<User, Error> {
let url = URL(string: "https://api.example.com/user")!
return URLSession.shared.dataTaskPublisher(for: url)
.map(.data)
.decode(type: User.self, decoder: JSONDecoder())
.eraseToAnyPublisher()
}
var cancellables = Set<AnyCancellable>()
fetchUser()
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { completion in
if case .failure(let error) = completion {
print("Error: (error)")
}
},
receiveValue: { user in
print("User: (user.name), Email: (user.email)")
}
)
.store(in: &cancellables)
2. 用户输入处理
swift
swift
import Combine
import UIKit
class SearchViewController: UIViewController {
@IBOutlet weak var searchField: UITextField!
@IBOutlet weak var resultsLabel: UILabel!
private var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
// 创建搜索文本的 Publisher
let searchPublisher = NotificationCenter.default
.publisher(for: UITextField.textDidChangeNotification, object: searchField)
.map { ($0.object as? UITextField)?.text ?? "" }
.debounce(for: .milliseconds(500), scheduler: RunLoop.main)
.removeDuplicates()
// 订阅搜索 Publisher
searchPublisher
.sink { [weak self] searchText in
self?.resultsLabel.text = "Searching for: (searchText)"
// 这里可以触发实际的搜索操作
}
.store(in: &cancellables)
}
}
3. 多个网络请求组合
swift
scss
func fetchUserAndPosts(userId: Int) -> AnyPublisher<(User, [Post]), Error> {
let userPublisher = fetchUser(userId: userId)
let postsPublisher = fetchPosts(userId: userId)
return userPublisher
.zip(postsPublisher)
.eraseToAnyPublisher()
}
fetchUserAndPosts(userId: 123)
.sink(
receiveCompletion: { completion in
if case .failure(let error) = completion {
print("Error: (error)")
}
},
receiveValue: { (user, posts) in
print("User: (user.name)")
print("Posts count: (posts.count)")
}
)
.store(in: &cancellables)
内存管理
Combine 使用 AnyCancellable
来管理订阅的生命周期。当 AnyCancellable
被释放时,订阅会自动取消。
swift
scss
var cancellables = Set<AnyCancellable>()
[1, 2, 3].publisher
.sink { print($0) }
.store(in: &cancellables) // 存储订阅以便管理生命周期
错误处理
Combine 提供了多种错误处理操作符:
1. Catch
swift
vbnet
func fetchData() -> AnyPublisher<Data, Error> {
let url = URL(string: "https://api.example.com/data")!
return URLSession.shared.dataTaskPublisher(for: url)
.map(.data)
.catch { error in
// 发生错误时返回备用数据
return Just(Data("Fallback data".utf8))
.setFailureType(to: Error.self)
}
.eraseToAnyPublisher()
}
2. Retry
swift
scss
URLSession.shared.dataTaskPublisher(for: url)
.map(.data)
.retry(3) // 失败时重试最多3次
.sink(...)
调度器(Scheduler)
Combine 允许你指定事件在哪个线程或队列上执行:
swift
scss
[1, 2, 3].publisher
.subscribe(on: DispatchQueue.global()) // 在后台线程执行
.map { $0 * 2 } // 在后台线程执行
.receive(on: DispatchQueue.main) // 切换到主线程
.sink { print($0) } // 在主线程执行
自定义 Publisher
swift
swift
struct TimerPublisher: Publisher {
typealias Output = Date
typealias Failure = Never
let interval: TimeInterval
func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
let subscription = TimerSubscription(interval: interval, subscriber: subscriber)
subscriber.receive(subscription: subscription)
}
}
class TimerSubscription<S: Subscriber>: Subscription where S.Input == Date, S.Failure == Never {
private var timer: Timer?
private let interval: TimeInterval
private var subscriber: S?
init(interval: TimeInterval, subscriber: S) {
self.interval = interval
self.subscriber = subscriber
startTimer()
}
private func startTimer() {
timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] _ in
_ = self?.subscriber?.receive(Date())
}
}
func request(_ demand: Subscribers.Demand) {
// 这里可以处理背压,但简单实现不需要
}
func cancel() {
timer?.invalidate()
subscriber = nil
}
}
// 使用自定义 Publisher
let timerPublisher = TimerPublisher(interval: 1)
let cancellable = timerPublisher
.sink { date in
print("Current date: (date)")
}
总结
Combine 是 iOS 开发中强大的响应式编程框架,它:
- 提供统一的异步事件处理方式
- 通过操作符链式组合,代码更简洁
- 内置丰富的操作符满足各种需求
- 与 SwiftUI 深度集成
- 良好的内存管理和错误处理机制
掌握 Combine 可以显著提高处理异步代码的效率和质量,特别是在复杂的异步操作和数据流处理场景中。