前言
Driver(老司機?),它是一個精心準備的特征序列,它主要是為了簡化 UI 層的代碼,也即是提供一種簡便的方式在 UI 層編寫響應式代碼。
Driver
在學習Driver之前,先來看一段rx的代碼:
let result = inputTF.rx.text.skip(1)
.flatMap { [weak self](input) -> Observable<Any> in
return (self?.dealwithData(inputText:input ?? ""))!
}
result.subscribe(onNext: { (element) in
print("訂閱到 \(element)")
})
result.subscribe(onNext: { (element) in
print("訂閱到 \(element)----\(Thread.current)")
})
func dealwithData(inputText:String)-> Observable<Any>{
print("開始請求網(wǎng)絡 \(Thread.current)")
return Observable<Any>.create({ (ob) -> Disposable in
if inputText == "123456" {
ob.onError(NSError.init(domain: "com.henry", code: 10010, userInfo: nil))
}
DispatchQueue.global().async {
print("發(fā)送信號之前: \(Thread.current)")
ob.onNext("發(fā)送的內容:\(inputText)")
ob.onCompleted()
}
return Disposables.create()
})
}
這段代碼的目的是:寫了一個網(wǎng)絡請求的方法(返回得到的是一個序列),然后利用UITextField輸入的內容,請求網(wǎng)絡,返回得到的請求數(shù)據(jù)(這里假裝請求了網(wǎng)絡,實際是將inputText的內容返回了),然后將這個值發(fā)送出去。
現(xiàn)在在UITextField中輸入1,來看看會打印出什么?
從結果可以看到寫的兩次訂閱都成功訂閱到了,那么問題就來了,
問題1:這里訂閱了兩次,但是也請求了兩次網(wǎng)絡,很明顯如果訂閱次數(shù)非常多次,網(wǎng)絡請求也就會非常多次,這樣的話會浪費網(wǎng)絡資源,占用帶寬。
問題2:從第二次訂閱的時候打印的線程可以看出來,網(wǎng)絡請求完數(shù)據(jù),訂閱到數(shù)據(jù)還在子線程,如果在這里面進行UI刷新的話,會出現(xiàn)問題。
問題3:在這里沒有對error事件進行訂閱,如果產(chǎn)生了error,會報錯誤而崩潰。
為了解決這幾個問題,我們可以將序列調用一個高階函數(shù)share(share會將觀察者共享序列),同時把序列放到主線程去,同時檢測error事件。
let result = inputTF.rx.text.skip(1)
.flatMap { [weak self](input) -> Observable<Any> in
return (self?.dealwithData(inputText:input ?? ""))!
.observeOn(MainScheduler())
//發(fā)生錯誤的處理
.catchErrorJustReturn("錯誤事件")
}.share(replay: 1, scope: .whileConnected)
這樣的話就解決了上面的問題;
下面來看一下利用Driver怎么來處理這部分代碼:
//asDriver()將普通序列轉換成Driver序列
let result = inputTF.rx.text.orEmpty
.asDriver()
.flatMap {
return self.dealwithData(inputText: $0)
// 僅僅提供發(fā)生錯誤時的備選返回值
.asDriver(onErrorJustReturn: "檢測到了錯誤事件")
}
//drive()方法綁定UI
result.map { "\($0 as! String)"}
.drive(self.btn.rx.title())
可以看到Driver完美的解決了上面的一系列問題,而且綁定UI必然實在主線程中,且不會因為網(wǎng)絡請求出錯而產(chǎn)生錯誤事件,而且是默認的序列共享。
那么也就是說,如果我們的序列滿足如下特征,就可以使用Driver:
- 不會產(chǎn)生 error 事件
- 一定在主線程監(jiān)聽(MainScheduler)
- 共享狀態(tài)變化(shareReplayLatestWhileConnected)
Driver源碼
那么Driver的底層到底是怎么實現(xiàn)的呢?下面來看一下Driver的源碼實現(xiàn):從前面已經(jīng)知道是asDriver()方法將源序列轉換成Driver序列的,那么先來看下asDriver()這個方法是再怎么實現(xiàn)的:
public func asDriver() -> Driver<Element> {
return self.asDriver { _ -> Driver<Element> in
#if DEBUG
rxFatalError("Somehow driver received error from a source that shouldn't fail.")
#else
return Driver.empty()
#endif
}
}
可以看到調用asDriver()方法是返回了self.asDriver的閉包,點擊此處的self.asDriver:
public func asDriver(onErrorRecover: @escaping (_ error: Swift.Error) -> Driver<Element>) -> Driver<Element> {
let source = self
.asObservable()
.observeOn(DriverSharingStrategy.scheduler)
.catchError { error in
onErrorRecover(error).asObservable()
}
return Driver(source)
}
從上面的代碼可以看出,先對源序列調用了observeOn()方法和catchError()方法。最后返回一個Driver對象。
observerOn()方法是用來指定線程的,這里指定為DriverSharingStrategy.scheduler這個DriverSharingStrategy.scheduler內部指定的就是主線程。
跟進去這個DriverSharingStrategy.scheduler的這個scheduler方法來看一下:
public struct DriverSharingStrategy: SharingStrategyProtocol {
public static var scheduler: SchedulerType { return SharingScheduler.make() }
}
這里返回了SharingScheduler.make(),好像不太明白,那就在跟進去這個make()方法來看下:
public private(set) static var make: () -> SchedulerType = { MainScheduler() }
可以看到在make()方法中調用了主線程。
這里就解決了Driver的執(zhí)行是在主線程的問題。
catchError()方法是用來處理錯誤事件的,當接收到錯誤事件信號后,可以把錯誤信號的備選值變成一個onNext:事件發(fā)送出去。
最后再來看一下返回的Driver對象,跟進去Driver看一下源碼:
public typealias Driver<Element> = SharedSequence<DriverSharingStrategy, Element>
這里Driver是取了別名的,它實際上就是SharedSequence(共享序列),再次進入到SharedSequence
public struct SharedSequence<SharingStrategy: SharingStrategyProtocol, Element> : SharedSequenceConvertibleType {
let _source: Observable<Element>
init(_ source: Observable<Element>) {
self._source = SharingStrategy.share(source)
}
在SharedSequence的init()方法中,調用了SharingStrategy.share(source)這個方法,可以知道SharingStrategy是SharedSequence的一個參數(shù)類型,通過這句public typealias Driver<Element> = SharedSequence<DriverSharingStrategy, Element>代碼可以知道,這里的SharingStrategy應該就是DriverSharingStrategy,所以下面進入到DriverSharingStrategy去:
public struct DriverSharingStrategy: SharingStrategyProtocol {
public static var scheduler: SchedulerType { return SharingScheduler.make() }
public static func share<Element>(_ source: Observable<Element>) -> Observable<Element> {
return source.share(replay: 1, scope: .whileConnected)
}
}
可以看到這里的share()方法,返回的source.share(replay: 1, scope: .whileConnected),有沒有感覺這句代碼很眼熟,沒錯就是在最開始優(yōu)化序列訂閱的時候寫的share()方法,實現(xiàn)序列的共享狀態(tài),防止多次網(wǎng)絡請求。這里就解決了多次訂閱,序列不共享狀態(tài)的問題。
總結
關于老司機Driver的學習就到這里,當我們需要利用序列來驅動程序的時候,Driver的使用還是非常有必要的。通過上面我們知道Driver就是把主線程,錯誤事件,共享狀態(tài)這三者封裝到一起了,確保了在寫代碼的過程中因為遺漏了某一步而導致程序崩潰。最后要注意的是:在綁定UI元素的時候我們使用的是drive而不是bindTo ,因為 drive 方法只能被 Driver 調用,這意味著,如果代碼存在 drive,那么這個序列不會產(chǎn)生錯誤事件并且一定在主線程監(jiān)聽。這樣我們就可以安全的綁定 UI 元素。