RxSwift(七)老司機(jī)特征序列之Driver詳解

前言

這篇文章從實(shí)際的代碼上去分析能真,在RxSwift中為什么要使用Driver以及應(yīng)該如何使用Driver

通過網(wǎng)絡(luò)請求綁定UI

現(xiàn)在模擬一種常用的情況碍讨, 監(jiān)聽UI -> 請求網(wǎng)絡(luò) -> 更新UI
如下代碼所示,這里是RxSwift的基本使用,監(jiān)聽textField的變化,請求網(wǎng)絡(luò)涂圆,然后更新UI

//監(jiān)聽textField變化,發(fā)送網(wǎng)絡(luò)請求
        let result  = inputTF.rx.text.skip(1)
            .flatMap { [weak self](input) -> Observable<Any> in
                return (self?.dealwithData(inputText:input ?? ""))!
        }.share(replay: 1, scope: .whileConnected)
        
        //給label賦值优妙,模擬更新某處UI
        _ = result.subscribe(onNext: { (element) in
            print("訂閱到了1")
            self.textLabel.text = element as? String
        }, onError: { (error) in
            
        })
        
        //給Btn賦值乘综,模擬更新另一處UI
        _ = result.subscribe(onNext: { (element) in
            print("訂閱到了2")
            self.btn.titleLabel?.text = element as? String
            }, onError: { (error) in
                
        })
        
        //模擬網(wǎng)絡(luò)請求憎账,還模擬了在監(jiān)測到錯誤輸入后返回錯誤的請看
    func dealwithData(inputText:String)-> Observable<Any>{
        print("請求網(wǎng)絡(luò)了 \(Thread.current)") // data
        return Observable<Any>.create({ (ob) -> Disposable in
            if inputText == "1234" {
                ob.onError(NSError.init(domain: "com.error.cn", code: 10086, userInfo: nil))
            }
            //模擬網(wǎng)絡(luò)請求發(fā)送時是子線程
            DispatchQueue.global().async {
                print("發(fā)送之前看看: \(Thread.current)")
                ob.onNext("已經(jīng)輸入:\(inputText)")
                ob.onCompleted()
            }
            return Disposables.create()
        })
    }

當(dāng)我們運(yùn)行代碼后套硼,發(fā)現(xiàn)有幾處問題

  1. self.textLabel.text = element as? String會引起崩潰,因?yàn)槲覀兪窃谧泳€程更新的UI胞皱,需要切換到主線程
  2. "請求網(wǎng)絡(luò)了" 打印了兩次邪意,說明訂閱了兩次,發(fā)送了兩個網(wǎng)絡(luò)請求反砌,這當(dāng)然不是我們想要的結(jié)果
  3. 注釋掉更新UI的代碼雾鬼,然后測試錯誤事件(輸入 1234),發(fā)現(xiàn)在發(fā)生錯誤后宴树,這個錯誤會取消所有綁定策菜,當(dāng)我們再輸入一個新的數(shù)字后,也無法產(chǎn)生響應(yīng)了

優(yōu)化代碼解決上面的問題

我們先用基本的RxSwift提供的函數(shù)解決這個問題酒贬,解決的函數(shù)看下面的注釋

    //監(jiān)聽textField變化又憨,發(fā)送網(wǎng)絡(luò)請求
        let result  = inputTF.rx.text.skip(1)
            .flatMap { [weak self](input) -> Observable<Any> in
                return (self?.dealwithData(inputText:input ?? ""))!
                        .observeOn(MainScheduler()) //切換到主線程,解決問題1
                        .catchErrorJustReturn("檢測到了錯誤事件") //捕獲錯誤锭吨,解決問題3
                        .share(replay: 1)   //共享網(wǎng)絡(luò)請求蠢莺,解決問題2
        }

再次運(yùn)行代碼,發(fā)現(xiàn)解決了上述問題零如,一切正常躏将,但是,我們能不能再優(yōu)化代碼呢考蕾?感覺上面的寫法特別麻煩祸憋,而且在一個大型系統(tǒng)內(nèi),要確保每一步不被遺漏是一件不太容易的事情肖卧。所以更好的選擇是合理運(yùn)用編譯器和特征序列來確保這些必備條件都已經(jīng)滿足蚯窥。

所以這時就可以開始用RxSwift的Driver特征序列了,畢竟是老司機(jī)序列,專為解決UI問題而生沟沙。

使用Driver進(jìn)行再次優(yōu)化

看看下面的代碼河劝,清爽干凈,解決所有問題矛紫,Driver的使用按照如下代碼就OK了

let result  = inputTF.rx.text.orEmpty
            .asDriver()
            .flatMap {
                return self.dealwithData(inputText: $0)
                    .asDriver(onErrorJustReturn: "檢測到了錯誤事件")
            }.map{ $0 as! String} //把Any轉(zhuǎn)為String
            
//訂閱代碼修改為:
//給label賦值赎瞎,模擬更新某處UI
result.drive(self.textLabel.rx.text)

//給Btn賦值,模擬更新另一處UI
result.drive(self.btn.rx.title())

分析一下上面的代碼:

  1. asDriver 把監(jiān)聽序列轉(zhuǎn)換成了Driver,任何可監(jiān)聽序列都可以被轉(zhuǎn)換為 Driver颊咬,只要他滿足 3 個條件:
    • 不會產(chǎn)生 error 事件
    • 一定在 MainScheduler 監(jiān)聽(主線程監(jiān)聽)
    • 共享附加作用(就類似上面的訂閱兩次务甥,但是只需要發(fā)一次網(wǎng)絡(luò)請求)
  2. onErrorJustReturn: [] 捕獲了錯誤
  3. 調(diào)用drive 直接把數(shù)據(jù)綁定到UI上。drive 方法只能被 Driver 調(diào)用喳篇。這意味著敞临,如果你發(fā)現(xiàn)代碼所存在 drive,那么這個序列不會產(chǎn)生錯誤事件并且一定在主線程監(jiān)聽麸澜。這樣你可以安全的綁定 UI 元素挺尿。

探索Driver的原理

和之前的幾篇文章一樣,這里探索一下Driver的實(shí)現(xiàn)原理炊邦,不過我這里不寫的那么詳細(xì)了编矾,大家可以自己嘗試一下探索源碼的樂趣。
沿著asDriver點(diǎn)擊進(jìn)去馁害,找到了它的方法實(shí)現(xiàn)窄俏,catchError就是實(shí)現(xiàn)了Driver能捕獲錯誤的功能了

    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訂閱的線程DriverSharingStrategy.scheduler是主線程MainScheduler,通過點(diǎn)擊進(jìn)去找源碼所發(fā)現(xiàn)的。所以Driver是在主線程跑的碘菜,可以更新UI

     public static var scheduler: SchedulerType { return SharingScheduler.make() }
     
     
     public private(set) static var make: () -> SchedulerType = { MainScheduler() }

點(diǎn)進(jìn)Driver(source)凹蜈,發(fā)現(xiàn)DriverSharedSequence的一個別名, 同時看到這里返回的是source.share(replay: 1, scope: .whileConnected),是不是和我們第一次優(yōu)化那段代碼時寫的一樣 ?
share會返回一個新的事件序列忍啸,監(jiān)聽底層 序列的事件仰坦,并且通知所有的訂閱者。
在上面的表現(xiàn)就是訂閱多次吊骤,也只會調(diào)用一次網(wǎng)絡(luò)請求缎岗。

public typealias Driver<Element> = SharedSequence<DriverSharingStrategy, Element>

  public static func share<Element>(_ source: Observable<Element>) -> Observable<Element> {
        return source.share(replay: 1, scope: .whileConnected)
    }

繼續(xù)探索一下share,通過這里makeSubject: { ReplaySubject.create(bufferSize: replay),我們發(fā)現(xiàn)它創(chuàng)建了一個ReplaySubject

     case .whileConnected:
            switch replay {
            case 0: return ShareWhileConnected(source: self.asObservable())
            case 1: return ShareReplay1WhileConnected(source: self.asObservable())
            default: return self.multicast(makeSubject: { ReplaySubject.create(bufferSize: replay) }).refCount()
            }

然后我們沿著multicast的方法一直往里找,發(fā)現(xiàn)它調(diào)用的是ConnectableObservableAdapter里的初始方法,發(fā)現(xiàn)這里保存了這個subject白粉,
然后這個subject是一個lazySubject传泊。

init(source: Observable<Subject.Observer.Element>, makeSubject: @escaping () -> Subject) {
        self._source = source
        self._makeSubject = makeSubject
        self._subject = nil
        self._connection = nil
    }
    
  fileprivate var lazySubject: Subject {
        if let subject = self._subject {
            return subject
        }

        let subject = self._makeSubject()
        self._subject = subject
        return subject
    }    

看到懶加載對象,突然就明白了這就是為什么訂閱多次鸭巴,只發(fā)一次網(wǎng)絡(luò)請求的原因了眷细,這里其實(shí)創(chuàng)建了一個新的Subject來監(jiān)聽序列,這個Subject只會創(chuàng)建一次鹃祖,所以它可以監(jiān)聽多個溪椎,但是只執(zhí)行一次。

總結(jié)

Driver 是一個精心準(zhǔn)備的特征序列。它主要是為了簡化 UI 層的代碼校读,所以在開發(fā)中是能夠比較常用到的沼侣,希望大家都能掌握并熟練使用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末歉秫,一起剝皮案震驚了整個濱河市蛾洛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌雁芙,老刑警劉巖轧膘,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異兔甘,居然都是意外死亡谎碍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門洞焙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蟆淀,“玉大人,你說我怎么就攤上這事闽晦“獍” “怎么了提岔?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵仙蛉,是天一觀的道長。 經(jīng)常有香客問我碱蒙,道長荠瘪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任赛惩,我火速辦了婚禮哀墓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘喷兼。我一直安慰自己篮绰,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布季惯。 她就那樣靜靜地躺著吠各,像睡著了一般。 火紅的嫁衣襯著肌膚如雪勉抓。 梳的紋絲不亂的頭發(fā)上贾漏,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音藕筋,去河邊找鬼纵散。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的伍掀。 我是一名探鬼主播掰茶,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蜜笤!你這毒婦竟也來了符匾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤瘩例,失蹤者是張志新(化名)和其女友劉穎啊胶,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體垛贤,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡焰坪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了聘惦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片某饰。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖善绎,靈堂內(nèi)的尸體忽然破棺而出黔漂,到底是詐尸還是另有隱情,我是刑警寧澤禀酱,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布炬守,位于F島的核電站,受9級特大地震影響剂跟,放射性物質(zhì)發(fā)生泄漏减途。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一曹洽、第九天 我趴在偏房一處隱蔽的房頂上張望鳍置。 院中可真熱鬧,春花似錦送淆、人聲如沸税产。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辟拷。三九已至,卻和暖如春环凿,著一層夾襖步出監(jiān)牢的瞬間梧兼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工智听, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留羽杰,地道東北人渡紫。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像考赛,于是被迫代替她去往敵國和親惕澎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345