RxSwift學習--特征序列之Driver

前言

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 元素。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容