這里是原文:Traits (formerly Units)
這一篇是整篇翻譯的后半部分捺檬,建議先看看前半部分:RxSwift之traits前篇:RxSwift traits
RxCocoa traits
Driver
這是最復(fù)雜的trait,它的目標(biāo)是提供一種簡(jiǎn)便的方式在UI層編寫(xiě)響應(yīng)式代碼秋泄,或者任何你想模擬驅(qū)動(dòng)你的App的數(shù)據(jù)流的情況。
- 不能拋出錯(cuò)誤
- 監(jiān)聽(tīng)發(fā)生在主線(xiàn)程
- 分享副作用(
shareReplayLatestWhileConnected
)
為什么命名為Driver
它的目標(biāo)用例是作為驅(qū)動(dòng)你的App的序列模型谱净。
例如:
- 由CoreData模型驅(qū)動(dòng)UI
- 使用其他UI元素(綁定)的值驅(qū)動(dòng)UI
就像正常的操作系統(tǒng)驅(qū)動(dòng)程序一樣放闺,假如一個(gè)序列拋出錯(cuò)誤凶赁,你的應(yīng)用程序就會(huì)停止響應(yīng)用戶(hù)操作观蜗。
那些元素在主線(xiàn)程被監(jiān)聽(tīng)臊恋,這極其重要,因?yàn)閁I元素和程序邏輯通常不是線(xiàn)程安全的墓捻。
此外抖仅,Driver建立了一個(gè)分享副作用的可觀(guān)察序列。
例如:
實(shí)際使用的例子
這是一個(gè)典型的初學(xué)者例子:
let results = query.rx.text
.throttle(0.3, scheduler: MainScheduler.instance)
.flatMapLatest { query in
fetchAutoCompleteItems(query)
}
results
.map { "\($0.count)" }
.bind(to: resultCount.rx.text)
.disposed(by: disposeBag)
results
.bind(to: resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
cell.textLabel?.text = "\(result)"
}
.disposed(by: disposeBag)```
以上代碼的預(yù)期行為是:
* 節(jié)流用戶(hù)輸入
* 聯(lián)系服務(wù)器并獲取用戶(hù)結(jié)果列表(每次查詢(xún)一次)
* 綁定結(jié)果到兩個(gè)UI元素:results table view和一個(gè)顯示結(jié)果數(shù)字的label
那么砖第,這個(gè)代碼有什么問(wèn)題呢撤卢?
* 如果`fetchAutoCompleteItems `可觀(guān)察序列拋出錯(cuò)誤(連接失敗或者是解析錯(cuò)誤),這個(gè)錯(cuò)誤將會(huì)解綁所有元素梧兼,并且UI不會(huì)再響應(yīng)新的查詢(xún)放吩。
* 如果`fetchAutoCompleteItems`在某個(gè)后臺(tái)線(xiàn)程返回結(jié)果,這些結(jié)果將會(huì)從一個(gè)后臺(tái)線(xiàn)程綁定到UI元素上羽杰,這將會(huì)導(dǎo)致不確定的崩潰屎慢。
* 結(jié)果被綁定到兩個(gè)UI元素上瞭稼,這就意味著忽洛,對(duì)于每個(gè)用戶(hù)查詢(xún)腻惠,都會(huì)建立兩個(gè)HTTP請(qǐng)求,每個(gè)UI元素對(duì)應(yīng)一個(gè)欲虚,這并不是預(yù)期的行為集灌。
一個(gè)更為合適的代碼版本如下:
let results = query.rx.text
.throttle(0.3, scheduler: MainScheduler.instance)
.flatMapLatest { query in
fetchAutoCompleteItems(query)
.observeOn(MainScheduler.instance) // results are returned on MainScheduler
.catchErrorJustReturn([]) // in the worst case, errors are handled
}
.shareReplay(1) // HTTP requests are shared and results replayed
// to all UI elements
results
.map { "($0.count)" }
.bind(to: resultCount.rx.text)
.disposed(by: disposeBag)
results
.bind(to: resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
cell.textLabel?.text = "(result)"
}
.disposed(by: disposeBag)```
確保所有這些要求在大型系統(tǒng)中被正確處理很有挑戰(zhàn)性,但有一種簡(jiǎn)單的使用編譯器和traits來(lái)證明這些要求得到滿(mǎn)足的方法复哆。
下面的代碼看起來(lái)幾乎一樣:
let results = query.rx.text.asDriver() // This converts a normal sequence into a `Driver` sequence.
.throttle(0.3, scheduler: MainScheduler.instance)
.flatMapLatest { query in
fetchAutoCompleteItems(query)
.asDriver(onErrorJustReturn: []) // Builder just needs info about what to return in case of error.
}
results
.map { "\($0.count)" }
.drive(resultCount.rx.text) // If there is a `drive` method available instead of `bindTo`,
.disposed(by: disposeBag) // that means that the compiler has proven that all properties
// are satisfied.
results
.drive(resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
cell.textLabel?.text = "\(result)"
}
.disposed(by: disposeBag)```
那么這里發(fā)生了什么欣喧?
第一個(gè)`asDriver`方法把trait`ControlProperty `轉(zhuǎn)換成trait`Driver `。
query.rx.text.asDriver()```
注意沒(méi)有任何特別的事情需要做梯找。Driver
擁有ControlProperty所有的屬性唆阿,加上一些其他的。底層的可觀(guān)察序列只是被封裝成了一個(gè)Driver
trait锈锤,就是這樣驯鳖。
第二個(gè)變化是:
.asDriver(onErrorJustReturn: [])```
任何可觀(guān)察序列都能被轉(zhuǎn)化為`Driver`,只要它滿(mǎn)足三個(gè)屬性:
* 不能拋出錯(cuò)誤
* 在主線(xiàn)程監(jiān)聽(tīng)
* 分享副作用(`shareReplayLatestWhileConnected `)
那么如何確保這些屬性得到滿(mǎn)足呢久免?只使用標(biāo)準(zhǔn)的Rx操作符浅辙。
`asDriver(onErrorJustReturn: []) `和以下代碼是等價(jià)的。
let safeSequence = xs
.observeOn(MainScheduler.instance) // observe events on main scheduler
.catchErrorJustReturn(onErrorJustReturn) // can't error out
.shareReplayLatestWhileConnected // side effects sharing
return Driver(raw: safeSequence) // wrap it up```
最后一件事是用drive
代替bindTo
阎姥。
drive
只在Driver
特性里定義记舆。這就意味著,如果你在某些代碼里看到drive
呼巴,那個(gè)可觀(guān)察序列絕不會(huì)拋出錯(cuò)誤泽腮,并且它在主線(xiàn)程監(jiān)聽(tīng),這對(duì)于綁定一個(gè)UI元素來(lái)說(shuō)是安全的衣赶。
注意诊赊,然而理論上,別人仍然可以為ObservableType
或者是一些其他接口定義一個(gè)drive
方法來(lái)工作屑埋,因此為了更加安全鸥咖,在綁定UI元素之前用let results: Driver<[Results]> = ...
創(chuàng)建一個(gè)臨時(shí)的定義是有必要的。然而些楣,我們將留給讀者來(lái)決定這是否是一個(gè)現(xiàn)實(shí)的方案另伍。
ControlProperty / ControlEvent
- 不能拋出錯(cuò)誤
- 訂閱發(fā)生在主線(xiàn)程
- 監(jiān)聽(tīng)發(fā)生在主線(xiàn)程
- 分享副作用