內(nèi)容概覽
- 前言
- Combine
- Publishers
- Subscribers
- Operators
前言
假設(shè)你需要構(gòu)建如下應(yīng)用:
App要求:
- 實時驗證用戶名是否有效
- 匹配密碼
- 響應(yīng)用戶界面
你會如何實現(xiàn)?實現(xiàn)的過程會應(yīng)用到哪些概念凿跳?
你需要使用 Target/Action 來獲取輸入框的最新值,使用計時器來實現(xiàn)定期檢查,使用 KVO 來控制加載指示器的狀態(tài)。
你還需要使用 URLSession 來進(jìn)行網(wǎng)絡(luò)請求,匯總?cè)齻€輸入框的結(jié)果癞松,然后通過 KVC 來控制按鈕的可用狀態(tài)。
涉及到的異步接口:
- Target/Action
- 通知中心
- URLSession
- KVC
- 定制的回調(diào)
是否覺得入蛆,這些步驟略顯繁瑣响蓉?
是否有更好的解決方案呢?
Combine
Combine 提供了統(tǒng)一的哨毁、聲明式的API枫甲,可以隨著時間的推移處理多個值
Combine 特性:
- 泛型
- 類型安全
- 組合優(yōu)先
- 由請求驅(qū)動
核心概念:
- Publishers
- Subscribers
- Operators
Publisher
- 定義值和錯誤如何產(chǎn)生
- 值類型
- 允許
Subscriber
的注冊
protocol Publisher {
associatedtype Output
associatedtype Failure: Error
func subscribe<S: Subscriber>(_ subscriber: S)
where S.Input == Output, S.Failure == Failure
}
為 Publisher 拓展通知中心
extension NotificationCenter {
struct Publisher: Combine.Publisher {
typealias Output = Notification
typealias Failure = Never
init(center: NotificationCenter, name: Notification.Name, object: Any? = nil)
}
}
Subscribers
- 接收值和完成事件
- 引用類型
protocol Subscriber {
associatedtype Input
associatedtype Failure: Error
func receive(subscription: Subscription)
func receive(_ input: Input) -> Subscribers.Demand
func receive(completion: Subscribers.Completion<Failure>)
}
extension Subscribers {
class Assign<Root, Input>: Subscriber, Cancellable {
typealias Failure = Never
}
init(object: Root, keyPath: ReferenceWritableKeyPath<Root, Input>)
}
Publisher 和 Subscriber 之間的關(guān)系
- Subscriber 被綁定到 Publisher 上
- Publisher 發(fā)送一個訂閱
- Subscriber 向 Publisher 請求值
- Publisher 向 Subscriber 發(fā)送值
- Publisher 發(fā)送完成事件
使用 Publisher 和 Subscriber 的示例代碼:
class Wizard {
var grade: Int
}
let merlin = Wizard(grade: 5)
let graduationPublisher = NotificationCenter.Publisher(center: .default, name: .graduated, object: merlin)
let gradeSubscriber = Subscribers.Assign(object: merlin, keyPath: \.grade)
graduationPublisher.subscribe(gradeSubscriber)
以上代碼由于類型不匹配,會遭遇如下錯誤:
看來扼褪,我們需要通過某種方式來實現(xiàn)類型匹配想幻。
Operators
- 輸入 Publisher
- 形容改變值的行為
- 訂閱 Publisher (向上游)
- 發(fā)送結(jié)果給 Subscriber (向下游)
- 值類型
extension Publishers {
struct Map<Upstream: Publisher, Output>: Publisher {
typealias Failure = Upstream.Failure
let upstream: Upstream
let transform: (Upstream.Output) -> Output
}
}
現(xiàn)在,我們可以通過優(yōu)雅的方式解決之前的問題话浇。
let graduationPublisher = NotificationCenter.Publisher(center: .default, name: .graduated, object: merlin)
let gradeSubscriber = Subscribers.Assign(object: merlin, keyPath: \.grade)
let converter = Publishers.Map(upstream: graduationPublisher) { note in
return note.userInfo?["NewGrade"] as? Int ?? 0
}
converter.subscribe(gradeSubscriber)
使用 Map Operator脏毯,我們完成了訂閱操作。
不過幔崖,還可以把步驟簡化一下:
extension Publisher {
func map<T>(_ transform: @escaping (Output) -> T) -> Publishers.Map<Self, T> {
return Publishers.Map(upstream: self, transform: transform)
}
}
let cancellable = NotificationCenter.default.publisher(for: .graduated, object: merlin)
.map { note in
return note.userInfo?["NewGrade"] as? Int ?? 0
}
.assign(to: \.grade, on: merlin)
聲明式 Operator API 的優(yōu)點:
- 函數(shù)式轉(zhuǎn)換
- 鏈?zhǔn)讲僮?/li>
- 錯誤處理
- 線程和隊列操作
- 調(diào)度和時間
在使用 Operator 時食店,優(yōu)先嘗試進(jìn)行組合
在對單個值(如:獨立的網(wǎng)絡(luò)請求)進(jìn)行異步操作時,推薦使用 Future
在對多個值(如:帳號密碼輸入框的事件流)進(jìn)行異步操作時赏寇,推薦使用 Publisher
使用 map 的部分其實還可以優(yōu)化
使用 compactMap 簡化操作:
進(jìn)行篩選:
截取部分值:
還可以組合多個 Publisher:
- Zip
- 將多個輸入的單次值組合后轉(zhuǎn)換為單個元組
- 是一個"when/and" 操作吉嫩,所有輸入都有輸入值才能產(chǎn)生輸出
- 要求具備所有輸入的輸入值時,才能產(chǎn)生輸入
- CombineLatest
- 將多個輸入的單次值組合后轉(zhuǎn)換為單個值
- 是一個"when/or"嗅定,其中一個輸入有輸入值就能產(chǎn)生輸出
- 只要有一個輸入有輸入值自娩,就會執(zhí)行輸出
- 會存儲所有輸入的最后一個輸入值
使用建議
- 使用
filter
處理通知中心的通知 - 使用
zip
處理等待兩個請求完成的事件 - 使用
decode
處理 URLResponse 的 data
更多內(nèi)容
- 錯誤處理和取消訂閱
- 調(diào)度和時間
- 設(shè)計模式
歡迎繼續(xù)閱讀 (WWDC) 實踐 Combine。
參考內(nèi)容:
Introducing Combine
轉(zhuǎn)載請注明出處渠退,謝謝~