前言
這篇文章從實(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)有幾處問題
-
self.textLabel.text = element as? String
會引起崩潰,因?yàn)槲覀兪窃谧泳€程更新的UI胞皱,需要切換到主線程 - "請求網(wǎng)絡(luò)了" 打印了兩次邪意,說明訂閱了兩次,發(fā)送了兩個網(wǎng)絡(luò)請求反砌,這當(dāng)然不是我們想要的結(jié)果
- 注釋掉更新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())
分析一下上面的代碼:
- asDriver 把監(jiān)聽序列轉(zhuǎn)換成了Driver,任何可監(jiān)聽序列都可以被轉(zhuǎn)換為 Driver颊咬,只要他滿足 3 個條件:
- 不會產(chǎn)生 error 事件
- 一定在 MainScheduler 監(jiān)聽(主線程監(jiān)聽)
- 共享附加作用(就類似上面的訂閱兩次务甥,但是只需要發(fā)一次網(wǎng)絡(luò)請求)
- onErrorJustReturn: [] 捕獲了錯誤
- 調(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)Driver
是SharedSequence
的一個別名, 同時看到這里返回的是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ā)中是能夠比較常用到的沼侣,希望大家都能掌握并熟練使用。