介紹
在現代 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)
}
}