介绍
在现代 iOS 开发中,响应式编程日益重要。Apple 推出的 Combine 框架为开发者提供了强大的声明式 API,用于处理异步事件流。本文将结合常见场景,逐一展示 Combine 的实际用法,包括网络请求、输入控制、定时器、通知监听、异步任务处理以及视图控制器之间的逆向传值。
网络请求
通过 Combine 可以优雅地封装网络请求流程。
let url = URL(string: "https://api.example.com/data")!
// Publisher
let publisher = URLSession.shared.dataTaskPublisher(for: url)
.map { $0.data }
.decode(type: Response.self, decoder: JSONDecoder())
.eraseToAnyPublisher()
// 订阅
let cancellable = publisher
.sink(receiveCompletion: { completion in
switch completion {
case .failure(let error):
print("Error: \(error)")
case .finished:
break
}
}, receiveValue: { response in
print("Response: \(response)")
})
控制输入
结合 @Published 和 debounce,可以高效地处理用户输入,避免频繁触发操作。
class ViewController: UIViewController {
@Published var text = ""
private var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
$text
.debounce(for: 0.5, scheduler: DispatchQueue.main)
.sink { [weak self] value in
guard let self = self else { return }
self.processInput(value)
}
.store(in: &cancellables)
}
func processInput(_ input: String) {
// 处理输入内容
print("Input: \(input)")
}
}
定时器
使用 Combine 的 Timer.publish 可以轻松创建定时器。
private var subscription: AnyCancellable?
subscription = Timer
.publish(every: 1, on: .main, in: .common)
.autoconnect()
.scan(0) { count, _ in // 累加,count为闭包最后一次返回的值
count + 1
}
.sink(receiveCompletion: { _ in
print("finish")
}, receiveValue: { [weak self] count in // 操作UI时,[weak self]不可少
guard let self = self else { return }
self.countLbl.text = count.format
})
通知
借助 NotificationCenter 和 Combine,可以优雅地响应通知事件。
private var subscriptions = Set<AnyCancellable>()
NotificationCenter
.default
.publisher(for: UITextField.textDidChangeNotification, object: inputTxtField) // 监听输入
.compactMap { ($0.object as? UITextField)?.text } // 此时的publisher是通知
.map { "The user entered: \($0)" }
.assign(to: \.text, on: textLbl)
.store(in: &subscriptions)
异步
通过 Future 可以将传统的回调封装为 Combine 的 Publisher。
// Publisher
func authorize() -> AnyPublisher<Bool, Error> {
// 延期Publisher等待订阅
Deferred {
// 任何异步操作都可以包进Future
Future { promise in
UNUserNotificationCenter
.current()
.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
// Promise处理
if let error = error {
// AnyPublisher的第2个参数
promise(.failure(error))
} else {
// AnyPublisher的第1个参数
promise(.success(granted))
}
}
}
}
.eraseToAnyPublisher()
}
private var subscriptions = Set<AnyCancellable>()
// 订阅
authorize() // AnyPublisher
.replaceError(with: false) // 异常处理
.receive(on: DispatchQueue.main)
.sink { [weak self] val in
guard let self = self else { return }
self.permissionLbl.text = "Status: \(val ? "Granted" : "Denied")"
}
.store(in: &subscriptions)
UIViewController逆向传值
当需要从下一个页面传值回当前页面时,Combine 提供了比 delegate 更优雅的方式。
// 接收值的UIViewController
let nextViewController = NextViewController()
let publisher = PassthroughSubject<String, Never>()
nextViewController.publisher = publisher
subscription = publisher.sink { [weak self] info in
guard let self = self else { return }
// 处理info
}
present(nextViewController, animated: true)
// 传递值的UIViewController
var publisher: PassthroughSubject<String, Never>?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
publisher?.send("传值")
dismiss(animated: true) {
self.publisher?.send(completion: .finished)
}
}