前言
看完本系列前面幾篇之后,估計大家也還是有點懵逼,本系列前八篇也都是參考RxSwift
官方文檔和一些概念做的解讀。上幾篇文章概念性的東西有點多,一時也是很難全部記住,大家腦子里面知道有這么個概念就行,用的時候,再來查閱一番,慢慢就掌握了。
本篇主要來深入了解一些RxSwift
實戰中用到的一些重要知識點,這里面有很多自己的理解,所以不免會有一些錯誤的地方,還請大家多多交流,如有發現錯誤的地方,歡迎評論。
概念
Rx
系列的核心就是Observable Sequence
這個相信大家心中已經有所了解了,這里不再啰嗦了,建議大家看看前面幾篇文章去了解一下。接下來介紹一些容易混淆和難以理解的概念。
- Observable 和 Observer
- subscribe 和 subscribe(onNext:)
- Dispose 和 DisposeBag
- observeOn() 和 subscribeOn()
- shareReplay
- 自定義operator
- Driver
- map 和 flatMap 何時使用
- UIBindingObserver
Observable 和 Observer
相信大家看前面幾篇文章的時候,大量出現這兩個東西,為了理解這兩個東西,我們先來簡單介紹下觀察者模式吧。
比如一個寶寶在睡覺,爸爸媽媽不可能時時刻刻待在那看着吧?那樣子太累
了。他們該做啥做啥,只要聽到寶寶哭聲的時候,他們給寶寶喂奶就行了。這就是一個簡單的觀察者模式。寶寶是被觀察者,爸爸媽媽是觀察者也稱作訂閱者,只要被觀察者發出了某一個事件,比如寶寶哭聲,叫聲都是一個事件,訂閱者就會做出相應地響應。
理解了觀察者模式這兩個概念就很好理解了,Observable
就是可被觀察的,也就是我們說的寶寶,他也是事件源。而Observer
就是我們的觀察者,也就是當收到事件的時候去做某些處理的爸爸媽媽。觀察者需要去訂閱(subscribe
)被觀察者,才能收到Observable
的事件通知消息。
subscribe 和 subscribe(onNext:)
subscribe
是訂閱sequence
發出的事件,比如next
事件,error
事件等。而subscribe(onNext:)
是監聽sequence
發出的next
事件中的element
進行處理,他會忽略error
和completed
事件。相對應的還有subscribe(onError:)
和 subscribe(onCompleted:)
。
Dispose 和 DisposeBag
當監聽一個sequence
的時候,有消息事件來了,我們做某些事情。但是這個sequence
不再發送消息事件了,那么我們的監聽也就沒有什么存在的價值了,所以我們需要釋放我們這些監聽資源,其實也就是內存資源釋放。
釋放某一個監聽的時候,我們有兩種方式處理:
我們可以手動調用釋放方式,但是我們一般不適用這種方式。
// 關於scheduler,我們會在下面講到
let subscription = Observable<Int>.interval(0.3, scheduler: SerialDispatchQueueScheduler.init(internalSerialQueueName: "scott"))
.observeOn(MainScheduler.instance) //observeOn也會在下面講到
.subscribe { event in
print(event)
}
Thread.sleep(forTimeInterval: 2.0)
subscription.dispose()
打印結果:
next(0)
next(1)
next(2)
next(3)
next(4)
next(5)
比如上面這個例子,我們創建了一個subscription
監聽,在兩秒以后我們不需要了,手動調用dispose()
方法,就能釋放監聽資源,不再打印信息。上面的subscription
不論是在哪個線程中監聽,就算在主線程中調用的dispose()
方法一樣會銷毀資源。
除了上述手動釋放資源外,還有一種自動方式,推薦大家使用這種方式,這種方式就像iOS
中的ARC
,會在適當的時候銷毀觀察者,自動釋放資源。
let disposeBag = DisposeBag()
Observable<Int>.empty()
.subscribe { event in
print(event)
}
.addDisposableTo(disposeBag)
如上個例子,我們創建一個disposeBag
來盛放我們需要管理的資源,然后把新建的監聽都放進去,會在適當的時候銷毀這些資源。如果你需要立即釋放資源只需要新建一個DisposeBag()
,那么上一個DisposeBag
就會被銷毀。
observeOn() 和 subscribeOn()
這兩個東西剛開始看的時候也是一臉懵逼,就知道最好多用observeOn()
,但是不知道為什么,下面我們就來揭開它們的面紗看下它們的真面目吧。
區別其實我感覺就一句話,subscribeOn()
設置起點在哪個線程,observeOn()
設置了后續工作在哪個線程。例如:
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
時,我也很疑惑,為什么很多的sequence
后面都有shareReplay(1)
呢?想的昏頭漲腦。
這里我就給大家講解一下我的理解吧。先看一個例子:
let disposeBag = DisposeBag()
let observable = Observable.just("🤣").map{print($0)}
observable.subscribe{print("Even:\($0)")}.disposed(by: disposeBag)
observable.subscribe{print("Even:\($0)")}.disposed(by: disposeBag)
打印結果:
🤣
Even:next(())
Even:completed
🤣
Even:next(())
Even:completed
大家發現沒有,map()
函數執行了兩次,但是有些時候,我們並不想讓map()
函數里面的東西執行兩次,比如map()
函數里面執行的是網絡請求,我只需要執行一次請求,然后把結果提供給大家使用就行了,多余的請求會增加負擔。所以這時候就需要使用shareReplay(1)
了。這里面的數字一般是1
,只執行一次。(ps:我改成 2,3 也只打印一次)
let disposeBag = DisposeBag()
let observable = Observable.just("🤣").map{print($0)}.shareReplay(1)
observable.subscribe{print("Even:\($0)")}.disposed(by: disposeBag)
observable.subscribe{print("Even:\($0)")}.disposed(by: disposeBag)
打印結果:
🤣
Even:next(())
Even:completed
Even:next(())
Even:completed
自定義operator
自定義操作符很簡單,官方推薦盡量使用標准的操作符,但是你也可以定義自己的操作符,文檔上說有兩種方法,這里介紹一下常用的一種方法吧。
例如我們自定義一個map
操作符:
extension ObserverType {
func myMap<R>(transform: E -> R) -> Observable<R> {
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
之前我們現在看看下面的🌰:
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()
保證后面所有操作在主線程完成。
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
來解決:
let results = query.rx.text.asDriver()
.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
有以下特點:
Driver
序列不允許發出error
,Driver
序列的監聽只會在主線程中。
所以Driver
是專為UI綁定量身打造的東西。
以下情況你可以使用Driver
替換BindTo
:
- 不能發出error;
- 在主線程中監聽;
- 共享事件流;
map 和 flatMap 何時使用
看了前面《RxSwift 系列(四) -- Transforming Operators》,我想大家對於何時使用map
和flatMap
也還是有疑惑。
我們來看看map
函數和flatMap
函數的定義:
map
函數,接收一個R類型的序列,返回一個R類型的序列,還是原來的序列。
public func map<R>(_ transform: @escaping (Self.E) throws -> R) -> RxSwift.Observable<R>
flatMap
函數,接收一個O類型的序列,返回一個O.E類型的序列,也就是有原來序列里元素組成的新序列。
public func flatMap<O: ObservableConvertibleType>(_ selector: @escaping (E) throws -> O)
-> Observable<O.E>
其實這里的map
和flatMap
在swift
中的作用是一樣的。map
函數可以對原有序列里面的事件元素進行改造,返回的還是原來的序列。而flatMap
對原有序列中的元素進行改造和處理,每一個元素返回一個新的sequence
,然后把每一個元素對應的sequence
合並為一個新的sequence
序列。
看下面例子:
let disposeBag = DisposeBag()
let observable = Observable.of("1","2","3","4","5").map{$0 + "scott"}
observable.subscribe(onNext: {print($0)}).disposed(by: disposeBag)
打印結果:
1scott
2scott
3scott
4scott
5scott
我們使用map
對序列中每一個元素進行了處理,返回的是一個元素,而使用flatMap
需要返回的序列。那么使用map
也返回一個序列看看。
let test = Observable.of("1", "2", "3", "4", "5")
.map { Observable.just($0) }
test.subscribe(onNext: {
print($0)
})
.addDisposableTo(disposeBag)
運行結果:
RxSwift.(Just in _BD9B9D4356C4038796FB16D0D54A9F8E)<Swift.String>
RxSwift.(Just in _BD9B9D4356C4038796FB16D0D54A9F8E)<Swift.String>
RxSwift.(Just in _BD9B9D4356C4038796FB16D0D54A9F8E)<Swift.String>
RxSwift.(Just in _BD9B9D4356C4038796FB16D0D54A9F8E)<Swift.String>
RxSwift.(Just in _BD9B9D4356C4038796FB16D0D54A9F8E)<Swift.String>
看到結果會打印出每一個序列,下面我們使用merge()
方法將這幾個序列進行合並:
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
合並為一個新序列后我們就可以正常打印元素了。下面看看使用faltMap()
函數干這件事:
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
看下對比是不是一樣,這樣子對比就清晰了吧。
- map函數只能返回原來的那一個序列,里面的參數的返回值被當做原來序列中所對應的元素。
- flatMap函數返回的是一個新的序列,將原來元素進行了處理,返回這些處理后的元素組成的新序列
- map函數 + 合並函數 = flatMap函數
flatMap
函數在實際應用中有很多地方需要用到,比如網絡請求,網絡請求可能會發生錯誤,我們需要對這個請求過程進行監聽,然后處理錯誤。只要繼續他返回的是一個新的序列。
UIBindingObserver
UIBindingObserver
這個東西很有用的,創建我們自己的監聽者,有時候RxCocoa
(RxSwift
中對UIKit
的一個擴展庫)給的擴展不夠我們使用,比如一個UITextField
有個isEnabled
屬性,我想把這個isEnabled
變為一個observer
,我們可以這樣做:
extension Reactive where Base: UITextField {
var inputEnabled: UIBindingObserver<Base, Result> {
return UIBindingObserver(UIElement: base) { textFiled, result in
textFiled.isEnabled = result.isValid
}
}
}
UIBindingObserver
是一個類,他的初始化方法中,有兩個參數,第一個參數是一個元素本身,第一個參數是一個閉包,閉包參數是元素本身,還有他的一個屬性。
public init(UIElement: UIElementType, binding: @escaping (UIElementType, Value) -> Swift.Void)
自定義了一個inputEnabled
關聯了UITextField
的isEnabled
屬性。
致謝
本系列文章理論性的東西就算是講述完了,如果你發現有錯誤,歡迎交流,共同進步,謝謝。接下來准備寫點實戰性的,大家准備好!