上一篇文章RxSwift入坑記-各種概念解讀內容比較多,文章簡直是太長了,我都難以堅持看下去?,建議大家粗略讀一遍就行了,用到的時候來查一下,慢慢地就掌握了。
這篇文章接著上篇文章,主要來深入瞭解一些RxSwift實戰中用到的一些重要知識點,這裡面有很多自己的理解和思考,包含很多網上幾乎收不到的內容,希望會是大家研究官方例子的一個重要參考資料,文章中不免會有些錯誤的地方,也請大家能多多留言交流,一起成長。這兩篇文章過後,準備寫實戰教程,希望大家多多關注吧。還有文章在我部落格裡閱讀更有感覺哦。let’s go!
Rx系列的核心就是Observable Sequence這個相信大家心中已經有所瞭解了,這裡不再囉嗦了,建議大家看看我都上一篇文章去了解一下。
Disposing
當監聽一個事件序列的時候,有訊息事件來了,我們做某些事情。但是這個事件序列不再發出訊息了,我們的監聽也就沒有什麼存在價值了,所以我們需要釋放我們這些監聽資源,其實也就是每種程式語言中的記憶體資源釋放。OC和Swift中也一樣,在你不需要用某些變數的時候,你需要把這些變數所佔用的記憶體空間釋放掉。
釋放某一個監聽的時候我們可以手動呼叫釋放方法,但是這個貌似一般不常用:
1 2 3 4 5 6 7 8 9 10 |
// 關於scheduler,我們會在下面講到 let subscription = Observable.interval(0.3, scheduler: SerialDispatchQueueScheduler.init(internalSerialQueueName: "test")) .observeOn(MainScheduler.instance) //observeOn也會在下面講到 .subscribe { event in print(event) } Thread.sleep(forTimeInterval: 2.0) subscription.dispose() |
1 2 3 4 5 6 |
next(0) next(1) next(2) next(3) next(4) next(5) |
比如上面這個例子,我們建立了一個subscription監聽,在兩秒以後我們不需要了,手動呼叫dispose()
方法,就能釋放監聽資源,不再列印資訊。上面的subscription不倫是在哪個執行緒中監聽,就算在主執行緒中呼叫的dispose()
方法一樣會銷燬資源。
Dispose Bags
除了上面手動的方法,還有一種是自動的方式,推薦大家使用這種方式,這種方式就好像iOS中的ARC方式似得,會自動去釋放資源。
1 2 3 4 5 6 7 |
let disposeBag = DisposeBag() Observable.empty() .subscribe { event in print(event) } .addDisposableTo(disposeBag) |
如上個例子,我們建立一個disposeBag來盛放我們需要管理的資源,然後把新建的監聽都放進去,會在適當的時候銷燬這些資源。如果你需要立即釋放資源只需要新建一個DisposeBag(),那麼上一個DisposeBag就會被銷燬。
Scheduler
這個東西其實就是執行緒,當你監聽一個事件序列的時候,會預設發生在當前執行緒,並且事件發出和事件處理都是在一個執行緒中完成,除非你中間更改了執行緒。這些東西我感覺沒必要做過多講解,用的時候查一下就好。
CurrentThreadScheduler
這是一個序列(Serial)佇列,也是預設執行緒
MainScheduler
這是主執行緒,也是一個序列佇列
SerialDispatchQueueScheduler
這是GCD中的序列佇列
ConcurrentDispatchQueueScheduler
這是GCD中的併發佇列
OperationQueueScheduler
這是一個NSOperationQueue併發佇列
如果你從併發佇列中轉到序列執行緒中,RxSwift也會把訊息變成序列訊息佇列。但是不建議從序列轉為併發。
observeOn()和subscribeOn()
這兩個東西可能很多人看官方文件說的一頭霧水,就知道最好多用observeOn(),但說明不了為啥。下面我們們就談談這倆貨到底有啥區別。
區別其實我感覺其實就一句話,subscribeOn()設定起點在哪個執行緒,observeOn()設定了後續工作在哪個執行緒。例如:
1 2 3 4 5 6 7 8 |
someObservable .doOneThing() 1 .observeOn(MainRouteScheduler.instance) 2 .subscribeOn(OtherScheduler.instance) 3 .subscribeNext { 4 ...... } .addDisposableTo(disposeBag) |
- 所有動作都發生在當前的預設執行緒
- observeOn轉換執行緒到主執行緒,下面所有的操作都是在主執行緒中
- subscribeOn規定動作一開始不是發生在預設執行緒了,而是在OtherScheduler了。
- 如果我們之前沒有呼叫observeOn,那麼這邊會在OtherScheduler發生,但是我們前面呼叫了observeOn,所以這個動作還是會在主執行緒中呼叫。
總結一下:subscribeOn只是影響事件鏈開始預設的執行緒,而observeOn規定了下一步動作發生在哪個執行緒中。
shareReplay
可能你看官方demo的時候,會有迷惑,為啥很多序列後面會有shareReplay(1)呢?,想的頭昏腦脹的。
請先看下面例子:
1 2 3 4 5 6 7 8 9 10 11 12 |
let testReplay = Observable.just("?") .map { print($0) } testReplay .subscribe { event in print(event) }.addDisposableTo(disposeBag) testReplay .subscribe { event in print(event) }.addDisposableTo(disposeBag) |
1 2 3 4 5 6 |
? next(()) completed ? next(()) completed |
大家發現沒,map函式執行了兩遍,但是有些時候我不需要map函式裡的東西執行兩遍,比如map函式裡面如果執行的是網路請求,我只需要一次請求結果供大家使用就行了,多餘的請求沒啥用,浪費時間。所以這時候就需要shareReplay(1)了。這裡面的數字一般都是1,只執行一次。你可以改為2,3看看結果有啥不同哦。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
let testReplay = Observable.just("?") .map { print($0) } .shareReplay(1) testReplay .subscribe { event in print(event) }.addDisposableTo(disposeBag) testReplay .subscribe { event in print(event) }.addDisposableTo(disposeBag) |
1 2 3 4 5 |
? //只執行了一次 next(()) completed next(()) completed |
自定義operator
自定義操作符很簡單,官方推薦儘量用標準的操作符,但是你也可以自定義自己的操作符,文件上說有兩種方法,這裡我們只說一下最常用的一種方法。
例如我們自定義一個map操作符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
extension ObservableType { func myMap(transform: E -> R) -> Observable { return Observable.create { observer in let subscription = self.subscribe { e in switch e { case .next(let value): let result = transform(value) observer.on(.next(result)) case .error(let error): observer.on(.error(error)) case .completed: observer.on(.completed) } } return subscription } } } |
引數是一個閉包,其中閉包引數是E型別返回值是R型別,map函式的返回值是一個Observable型別。
Driver
Driver是啥東東?Driver功能很吊,講解Driver之前我們現在看看下面的例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
let results = query.rx.text .throttle(0.3, scheduler: MainScheduler.instance) .flatMapLatest { query in fetchAutoCompleteItems(query) } results .map { "\($0.count)" } .bindTo(resultCount.rx.text) .addDisposableTo(disposeBag) results .bindTo(resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in cell.textLabel?.text = "\(result)" } .addDisposableTo(disposeBag) |
- 首先建立一個可監聽序列results,其中flatMapLatest怎麼用我們下面講
- 然後將results繫結到resultCount.rx.text上
- 將results繫結到resultsTableView上
上面程式會有下面幾個異常情況
- 如果上面fetchAutoCompleteItems出錯了,那麼他繫結的UI將不再收到任何事件訊息
- 如果上面fetchAutoCompleteItems是在後臺某個執行緒中執行的,那麼事件繫結也是發生在後臺某個執行緒,這樣更新UI的時候會造成crash
- 有兩次繫結fetchAutoCompleteItems會執行兩次
當然針對上面問題我們也有解決方案,我們可以使用神器shareReplay(1)保證不會執行兩次,可以使用observeOn()保證後面所有操作在主執行緒完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
let results = query.rx.text .throttle(0.3, scheduler: MainScheduler.instance) .flatMapLatest { query in fetchAutoCompleteItems(query) .observeOn(MainScheduler.instance) .catchErrorJustReturn([]) } .shareReplay(1) results .map { "\($0.count)" } .bindTo(resultCount.rx.text) .addDisposableTo(disposeBag) results .bindTo(resultTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in cell.textLabel?.text = "\(result)" } .addDisposableTo(disposeBag) |
但是你也可以使用Driver
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
let results = query.rx.text.asDriver() //轉換成一個Driver序列 .throttle(0.3, scheduler: MainScheduler.instance) .flatMapLatest { query in fetchAutoCompleteItems(query) .asDriver(onErrorJustReturn: []) //當遇見錯誤需要返回什麼 } //不需要新增shareReplay(1) results .map { "\($0.count)" } .drive(resultCount.rx.text) //和bingTo()功能一樣 .addDisposableTo(disposeBag) results .drive(resultTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in cell.textLabel?.text = "\(result)" } .addDisposableTo(disposeBag) |
drive方法只能在Driver序列中使用,Driver有以下特點:1 Driver序列不允許發出error,2 Driver序列的監聽只會在主執行緒中。所以Driver是轉為UI繫結量身打造的東西。以下情況你可以使用Driver替換BindTo:
- 不能發出error
- 在主執行緒中監聽
- 共享事件流
map和flatMap何時使用
大家看官方Demo的時候,可能會迷惑為啥有的地方使用flatMapLatest為啥有些地方使用map呢?比如上面那個Driver所用的例子。
map函式,接受一個R型別的序列,返回一個R型別的序列,還是原來的序列
1 |
public func map(_ transform: @escaping (Self.E) throws -> R) -> RxSwift.Observable |
flatMap函式,接收一個O型別的序列,返回一個O.E型別的序列,也就是有原來序列裡元素組成的新序列。
1 |
public func flatMap(_ selector: @escaping (Self.E) throws -> O) -> RxSwift.Observable |
其實這裡的map和flatMap在swift中的作用是一樣的。map函式可以對原有序列裡面的事件元素進行改造,返回的還是原來的序列。而flatMap對原有序列中的元素進行改造和處理,每一個元素返回一個新的sequence,然後把每一個元素對應的sequence合併為一個新的sequence序列。
看下面例子:
1 2 3 4 5 6 7 8 |
let test = Observable.of("1", "2", "3", "4", "5") .map { $0 + "TTF" } test .subscribe(onNext: { print($0) }) .addDisposableTo(disposeBag) |
1 2 3 4 5 |
1TTF 2TTF 3TTF 4TTF 5TTF |
我們使用map對序列中每一個元素進行了處理,返回的是一個元素,而使用flatMap需要返回的序列。那麼使用map也返回一個序列看看。
1 2 3 4 5 6 7 8 |
let test = Observable.of("1", "2", "3", "4", "5") .map { Observable.just($0) } test .subscribe(onNext: { print($0) }) .addDisposableTo(disposeBag) |
1 2 3 4 5 |
RxSwift.Just RxSwift.Just RxSwift.Just RxSwift.Just RxSwift.Just |
看到結果會列印出每一個序列,下面我們使用merge()方法將這幾個序列進行合併
1 2 3 4 5 6 7 8 9 |
let test = Observable.of("1", "2", "3", "4", "5") .map { Observable.just($0) } .merge() test .subscribe(onNext: { print($0) }) .addDisposableTo(disposeBag) |
1 2 3 4 5 |
1 2 3 4 5 |
合併為一個新序列後我們就可以正常列印元素了。下面看看使用faltMap()函式幹這件事
1 2 3 4 5 6 7 8 |
let test = Observable.of("1", "2", "3", "4", "5") .flatMap { Observable.just($0) } test .subscribe(onNext: { print($0) }) .addDisposableTo(disposeBag) |
1 2 3 4 5 |
1 2 3 4 5 |
看下對比是不是一樣,這樣子對比就清晰了吧。
- map函式只能返回原來的那一個序列,裡面的引數的返回值被當做原來序列中所對應的元素。
- flatMap函式返回的是一個新的序列,將原來元素進行了處理,返回這些處理後的元素組成的新序列
- map函式 + 合併函式 = flatMap函式
flatMap函式在實際應用中有很多地方需要用到,比如網路請求,網路請求可能會發生錯誤,我們需要對這個請求過程進行監聽,然後處理錯誤。只要繼續他返回的是一個新的序列。
1 2 3 4 5 6 7 |
validatedUsername = input.username .flatMapLatest { username in return validationService.validateUsername(username) .observeOn(MainScheduler.instance) .catchErrorJustReturn(.failed(message: "Error contacting server")) } .shareReplay(1) |
flatMapLatest其實就是flatMap的另一個方式,只傳送最後一個合進來的序列事件。上面認證username是一個網路請求,我們需要對這個過程進行處理。
1 2 3 4 5 |
validatedPassword = input.password .map { password in return validationService.validatePassword(password) } .shareReplay(1) |
這個password不需要後臺聯網認證,只需要返回password符合不符合要求就行了,還是原來的序列就行了。
flatMap也解決了內嵌多個subscribe的問題,官方不建議內嵌多個subscribe。比如:
1 2 3 4 5 6 7 |
textField.rx_text.subscribe(onNext: { text in performURLRequest(text).subscribe(onNext: { result in ... }) .addDisposableTo(disposeBag) }) .addDisposableTo(disposeBag) |
改寫為flatMap
1 2 3 4 5 6 |
textField.rx_text .flatMapLatest { text in return performURLRequest(text) //因為flatMap返回一個新的sequence } ... .addDisposableTo(disposeBag) |
好了,相信大家對這倆貨有了一個清晰的認識了吧。
UIBindingObserver
UIBindingObserver這個東西很有用的,建立我們自己的監聽者,有時候RxCocoa(RxSwiftz中對UIKit的一個擴充套件庫)給的擴充套件不夠我們使用,比如一個UITextField有個isEnabled屬性,我想把這個isEnabled變為一個observer,我們可以這樣做:
1 2 3 4 5 6 7 |
extension Reactive where Base: UITextField { var inputEnabled: UIBindingObserver { return UIBindingObserver(UIElement: base) { textFiled, result in textFiled.isEnabled = result.isValid } } } |
UIBindingObserver是一個類,他的初始化方法中,有兩個引數,第一個引數是一個元素本身,第一個引數是一個閉包,閉包引數是元素本身,還有他的一個屬性。
1 |
public init(UIElement: UIElementType, binding: @escaping (UIElementType, Value) -> Swift.Void) |
自定義了一個inputEnabled Observer裡面關聯的UITextField的isEnabled屬性。
好了,文章到這裡也差不多了,這篇文章中沒有實戰教程,但絕對都是乾貨,也許在你專研官方demo的時候看不懂某個寫法,看了這篇文章你就會豁然開朗了呢??
下一篇文章準備帶大家一起實戰,大家做好準備!!?
小夥伴們如果感覺文章可以,可以關注博主部落格
小夥伴們也可以關注博主微博,探索博主內心世界?
如要轉載請註明出處。