是時(shí)候?qū)W習(xí) RxSwift 了
相信在過去的一段時(shí)間里,對(duì) RxSwift 多少有過接觸或耳聞,或者已經(jīng)積累了不少實(shí)戰(zhàn)經(jīng)驗(yàn)。此文主要針對(duì)那些在門口徘徊,想進(jìn)又拍踩坑的同學(xué)。
為什么要學(xué)習(xí) RxSwift
當(dāng)決定做一件事情時(shí),至少要知道為什么。RxSwift 官網(wǎng)舉了幾個(gè)例子,比如可以統(tǒng)一處理 Delegate, KVO, Notification,可以綁定 UI,方便網(wǎng)絡(luò)請(qǐng)求的處理等等。但這些更多的是描述可以用 RxSwift 來做什么,跟為什么要使用 RxSwift 還是會(huì)有點(diǎn)不同。
我們先來分析下 GUI 編程的本質(zhì),我喜歡把它抽象為視圖和數(shù)據(jù)的結(jié)合。其中視圖負(fù)責(zé)兩件事:展示和交互,展示什么由數(shù)據(jù)決定。

其中單向數(shù)據(jù)流可以通過之前介紹的 ReSwift 完成??雌饋砗孟駴] RxSwift 什么事情,其實(shí)不然,RxSwift 可以在 UniDirectional Data Flow 的各個(gè)階段都發(fā)揮作用,從而讓 Data 的處理和流動(dòng)更加簡潔和清晰。

- 通過對(duì) RxCocoa 的各種回調(diào)進(jìn)行統(tǒng)一處理,方便了「Interact」的處理。
- 通過對(duì)
Observable的 transform 和 composite,方便了Action的生成(比如使用throttle來壓縮Action)。 - 通過對(duì)網(wǎng)絡(luò)請(qǐng)求以及其他異步數(shù)據(jù)的獲取進(jìn)行
Observable封裝,方便了異步數(shù)據(jù)的處理。 - 通過 RxCocoa 的 binding,方便了數(shù)據(jù)的渲染。
所以 ReSwift 規(guī)范了數(shù)據(jù)流,RxSwift 為數(shù)據(jù)的處理提供了方便,這兩個(gè)類庫的結(jié)合,可以產(chǎn)生清晰的架構(gòu)和易維護(hù)的代碼。
當(dāng)然,前提是對(duì)它們有足夠的了解,尤其是 RxSwift,也就是我們今天的主角。
什么是 RxSwift
在 GUI 編程中,我認(rèn)為比較復(fù)雜的有三個(gè)部分:
- 非原生 UI 效果的實(shí)現(xiàn)(比如產(chǎn)品經(jīng)理們經(jīng)常冒出來的各種想法)。
- 大量狀態(tài)的維護(hù)。
- 異步數(shù)據(jù)的處理。
1)不在這次的討論范疇(這里的學(xué)問也很多,比如流暢性和性能)。2) 可以通過單向數(shù)據(jù)流來解決(結(jié)合 Immutable Data)。3) 可以通過 RxSwift 來解決。那么 RxSwift 是如何處理異步數(shù)據(jù)的呢?
在說 RxSwift 之前,先來說下 Rx, ReactiveX 是一種編程模型,最初由微軟開發(fā),結(jié)合了觀察者模式、迭代器模式和函數(shù)式編程的精華,來更方便地處理異步數(shù)據(jù)流。其中最重要的一個(gè)概念是 Observable。
舉個(gè)簡單的例子,當(dāng)別人在跟你說話時(shí),你就是那個(gè)觀察者,別人就是那個(gè) Observable,它有幾個(gè)特點(diǎn):
- 可能會(huì)不斷地跟你說話。(
onNext:) - 可能會(huì)說錯(cuò)話。(
onError:) - 結(jié)束會(huì)說話。(
onCompleted)
你在聽到對(duì)方說的話后,也可以有幾種反應(yīng):
- 根據(jù)說的話,做相應(yīng)的事,比如對(duì)方讓你借本書給他。(
subscribe) - 把對(duì)方說的話,加工下再傳達(dá)給其他人,比如對(duì)方說小張好像不太舒服,你傳達(dá)給其他人時(shí)就變成了小張失戀了。(
map:) - 參考其他人說的話再做處理,比如 A 說某家店很好吃,B 說某家店一般般,你需要結(jié)合兩個(gè)人的意見再做定奪。(
zip:)
所以,從生活中也能看到 Rx 的影子?!赣行┦虑榧辈坏茫愕玫人约菏臁?,異步,其實(shí)就是跟時(shí)間打交道,不同的時(shí)間,拿到的數(shù)據(jù)也會(huì)不一樣。可以在線感受下

這里的核心是當(dāng)數(shù)據(jù)有變化時(shí),能夠立刻知曉,并且通過組合和轉(zhuǎn)換后,可以即時(shí)作出響應(yīng)。有點(diǎn)像塔防,先在路上的各個(gè)節(jié)點(diǎn)埋好武器,然后等著小怪獸們過來。
RxSwift Workflow
大致分為這么幾個(gè)階段:先把 Native Object 變成 Observable,再通過 Observable 內(nèi)置的各種強(qiáng)大的轉(zhuǎn)換和組合能力變成新的 Observable,最后消費(fèi)新的 Observable 的數(shù)據(jù)。

Native Object -> Observable
.rx extension
假設(shè)需要處理點(diǎn)擊事件,正常的做法是給 Tap Gesture 添加一個(gè) Target-Action,然后在那里實(shí)現(xiàn)具體的邏輯,這樣的問題在于需要重新取名字,而且丟失了上下文。RxSwift (確切說是 RxCocoa) 給系統(tǒng)的諸多原生控件(包括像 URLSession)提供了 rx 擴(kuò)展,所以點(diǎn)擊的處理就變成了這樣:
let tapBackground = UITapGestureRecognizer()
tapBackground.rx.event
.subscribe(onNext: { [weak self] _ in
self?.view.endEditing(true)
})
.addDisposableTo(disposeBag)
view.addGestureRecognizer(tapBackground)
是不是簡潔了很多。
Observable.create
通過這個(gè)方法,可以將 Native 的 object 包裝成 Observable,比如對(duì)網(wǎng)絡(luò)請(qǐng)求的封裝:
public func response(_ request: URLRequest) -> Observable<(Data, HTTPURLResponse)> {
return Observable.create { observer in
let task = self.dataTaskWithRequest(request) { (data, response, error) in
observer.on(.next(data, httpResponse))
observer.on(.completed)
}
task.resume()
return Disposables.create {
task.cancel()
}
}
}
出于代碼的簡潔,略去了對(duì) error 的處理,使用姿勢類似
let disposeBag = DisposeBag()
response(aRequest)
.subscribe(onNext: { data in
print(data)
})
.addDisposableTo(disposeBag)
這里有兩個(gè)注意點(diǎn):
-
Observerable返回的是一個(gè)Disposable,表示「可扔掉」的,扔哪里呢,就扔到剛剛創(chuàng)建的袋子里,這樣當(dāng)袋子被回收(dealloc)時(shí),會(huì)順便執(zhí)行一下Disposable.dispose(),之前創(chuàng)建Disposable時(shí)申請(qǐng)的資源就會(huì)被一并釋放掉。 - 如果有多個(gè) subscriber 來 subscribe
response(aRequest)那么會(huì)創(chuàng)建多個(gè)請(qǐng)求,從代碼也可以看得出來,來一個(gè) observer 就創(chuàng)建一個(gè) task,然后執(zhí)行。這很有可能不是我們想要的,如何讓多個(gè) subscriber 共享一個(gè)結(jié)果,這個(gè)后面會(huì)提到。
Variable()
Variable(value) 可以把 value 變成一個(gè) Observable,不過前提是使用新的賦值方式 aVariable.value = newValue,來看個(gè) Demo
let magicNumber = 42
let magicNumberVariable = Variable(magicNumber)
magicNumberVariable.asObservable().subscribe(onNext: {
print("magic number is \($0)")
})
magicNumberVariable.value = 73
// output
//
// magic number is 42
// magic number is 73
起初看到時(shí),覺得還蠻神奇的,跟進(jìn)去看了下,發(fā)現(xiàn)是通過 subject 來做的,大意是把 value 存到一個(gè)內(nèi)部變量 _value 里,當(dāng)調(diào)用 value 方法時(shí),先更新 _value 值,然后調(diào)用內(nèi)部的 _subject.on(.next(newValue)) 方法告知 subscriber。
Subject
Subject 簡單來說是一個(gè)可以主動(dòng)發(fā)射數(shù)據(jù)的 Observable,多了 onNext(value), onError(error), ‘onCompleted’ 方法,可謂全能型選手。
let disposeBag = DisposeBag()
let subject = PublishSubject<String>()
subject.addObserver("1").addDisposableTo(disposeBag)
subject.onNext("??")
subject.onNext("??")
subject.addObserver("2").addDisposableTo(disposeBag)
subject.onNext("???")
subject.onNext("???")
記得在 RAC 時(shí)代,subject 是一個(gè)不太推薦使用的功能,因?yàn)檫^于強(qiáng)大了,容易失控。RxSwift 里倒是沒有太提及,但還是少用為佳。
Observable -> New Observable
Observable 的強(qiáng)大不僅在于它能實(shí)時(shí)更新 value,還在于它能被修改/過濾/組合等,這樣就能隨心所欲地構(gòu)造自己想要的數(shù)據(jù),還不用擔(dān)心數(shù)據(jù)發(fā)生變化了卻不知道的情況。
Combine
Combine 就是把多個(gè) Observable 組合起來使用,比如 zip (小提示:如果對(duì)這些函數(shù)不太敏感,可以實(shí)際操作下,體會(huì)會(huì)更深些)
zip 對(duì)應(yīng)現(xiàn)實(shí)中的例子就是拉鏈,拉鏈需要兩個(gè)元素這樣才能拉上去,這里也一樣,只有當(dāng)兩個(gè) Observable 都有了新的值時(shí),subscribe 才會(huì)被觸發(fā)。
let stringSubject = PublishSubject<String>()
let intSubject = PublishSubject<Int>()
Observable.zip(stringSubject, intSubject) { stringElement, intElement in
"\(stringElement) \(intElement)"
}
.subscribe(onNext: { print($0) })
.addDisposableTo(disposeBag)
stringSubject.onNext("???")
stringSubject.onNext("???")
intSubject.onNext(1)
intSubject.onNext(2)
// output
//
// ??? 1
// ??? 2
如果這里 intSubject 始終沒有執(zhí)行 onNext,那么將不會(huì)有輸出,就像拉鏈掉了一邊的鏈子就拉不上了。
除了 zip,還有其他的 combine 的姿勢,比如 combineLatest / switchLatest 等。
Transform
這是最常見的操作了,對(duì)一個(gè) Observable 的數(shù)值做一些小改動(dòng),然后產(chǎn)出新的值,依舊是一個(gè) Observable。
let disposeBag = DisposeBag()
Observable.of(1, 2, 3)
.map { $0 * $0 }
.subscribe(onNext: { print($0) })
.addDisposableTo(disposeBag)
這是大致的實(shí)現(xiàn)(摘自官網(wǎng))
extension ObservableType {
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
}
}
}
接受一個(gè) transform 閉包,然后返回一個(gè) Observable,因?yàn)榻酉聛硎褂谜邔?huì)對(duì) myMap 的結(jié)果進(jìn)行 subscribe,所以需要在 create 內(nèi)部 subscribe 一下,不然最開始的那個(gè) Observable 就是個(gè) Cold Observable,一個(gè) Cold Observable 是不會(huì)產(chǎn)生新的數(shù)據(jù)的。
Filter
Filter 的作用是對(duì) Observable 傳過來的數(shù)據(jù)進(jìn)行過濾,只有符合條件的才有資格被 subscribe。寫法上跟 map 差不多,就不贅述了。
Connect
這是挺有意思的一塊,在之前介紹 Observable.create 時(shí)有提到過,一個(gè) Observable 被多次 subscribe 就會(huì)被多次觸發(fā),如果一個(gè)網(wǎng)絡(luò)請(qǐng)求只想被觸發(fā)一次,同時(shí)支持多個(gè) subscriber,就可以使用 publish + connect 的組合。
當(dāng)一個(gè) Observable 使用了 publish() 方法后,正常的 subscribe 就不會(huì)觸發(fā)它了,除非 connect() 方法被調(diào)用。而且每次 subscribe 不會(huì)導(dǎo)致 Observable 重新針對(duì) observer 處理一遍??匆幌逻@張圖

有兩塊需要注意:
-
connect()之前的兩次subscribe并沒有產(chǎn)生新的 value。 -
connect()之后subscribe的,只是等待新的 value,同時(shí)新的 value 還會(huì)分發(fā)給之前的 subscriber。 - 即使所有的
subscription被dispose,Observable依舊處于hot狀態(tài),就好像還以為有人關(guān)心新的值一樣。(這可能不是想要的結(jié)果)
針對(duì)第 3 點(diǎn),可以使用 refcount() 來代替 connect(),前者會(huì)在沒有 subscriber 時(shí)自動(dòng)「冷」下來,不會(huì)再產(chǎn)生新的值。(Demo 取自這里)
let myObservable = Observable<Int>.interval(1, scheduler: MainScheduler.instance).publish().refCount() // 1)
let mySubscription = myObservable.subscribe(onNext: {
print("Next: \($0)")
})
delay(3) {
print("Disposing at 3 seconds")
mySubscription.dispose()
}
delay(6) {
print("Subscribing again at 6 seconds")
myObservable.subscribe(onNext: {
print("Next: \($0)")
})
}
輸出
Starting at 0 seconds
Next: 0
Next: 1
Next: 2
Disposing at 3 seconds
Subscribing again at 6 seconds
Next: 0
Next: 1
可以看到,3 秒后 subscription dispose,此時(shí)沒有任何 subscriber 還關(guān)心 Observable,因此就重置了,所以 6 秒后又回到了初始狀態(tài)(如果變成 connect 方法的話,會(huì)發(fā)現(xiàn) 6 秒后會(huì)輸出 Next: 6 / Next: 7)
那如果后加入的 subscriber 想要之前的數(shù)據(jù)怎么辦?可以對(duì)原始的 Observable 設(shè)置 replay(n),表示最多返回 n 個(gè)元素給后加入的 subscriber。
Tips
上面介紹的是最基本的概念。順便提一下比較常見的幾個(gè)問題:
如何處理 Scheduler?
默認(rèn)代碼都是在當(dāng)前線程中執(zhí)行的,如果要手動(dòng)切換線程,可以使用 subsribeOn 和 observeOn 兩種方式,一般來說后者用得會(huì)多一些,那這兩者有什么區(qū)別呢?
subscribeOn 跟位置無關(guān),也就是無論在鏈?zhǔn)秸{(diào)用的什么地方,Observable 和 subscription 都會(huì)受影響;而 observeOn 則僅對(duì)之后的調(diào)用產(chǎn)生影響,看個(gè) Demo:
var observable = Observable<Int>.create { (observer: AnyObserver<Int>) -> Disposable in
print("observable thread: \(Thread.current)")
observer.onNext(1)
observer.onCompleted()
return Disposables.create()
}
let disposeBag = DisposeBag()
observable
.map({ (e) -> Int in
print("map1 thread: \(Thread.current)")
return e + 1
})
.observeOn(ConcurrentDispatchQueueScheduler(qos: .userInteractive)) // 1
.map({ (e) -> Int in
print("map2 thread: \(Thread.current)")
return e + 2
})
.subscribe(onNext:{ (e) -> Void in
print("subscribe thread: \(Thread.current)")
})
.addDisposableTo(disposeBag)
如果 1) 是 observeOn,那么輸出如下
observable thread: <NSThread: 0x7f901cc0d510>{number = 1, name = main}
map1 thread: <NSThread: 0x7f901cc0d510>{number = 1, name = main}
map2 thread: <NSThread: 0x7f901ce15560>{number = 3, name = (null)}
subscribe thread: <NSThread: 0x7f901ce15560>{number = 3, name = (null)}
可以看到 observable thread 和 map1 thread 依舊保持當(dāng)前線程,但 observeOn 之后就變成了另一個(gè)線程。
如果 1) 是 subscribeOn,那么會(huì)輸出
observable thread: <NSThread: 0x7fbdf1e097a0>{number = 3, name = (null)}
map1 thread: <NSThread: 0x7fbdf1e097a0>{number = 3, name = (null)}
map2 thread: <NSThread: 0x7fbdf1e097a0>{number = 3, name = (null)}
subscribe thread: <NSThread: 0x7fbdf1e097a0>{number = 3, name = (null)}
可以看到全都變成了 subscribeOn 指定的 Queue。所以 subscribeOn 的感染力很強(qiáng),連 Observable 都能影響到。
Cold Observable 和 Hot Observable
Cold 相當(dāng)于 InActive,就像西部世界里,未被激活的機(jī)器人一樣;Hot 就是處于工作狀態(tài)的機(jī)器人。
Subscription 為什么要 Dispose?
因?yàn)橛辛?Subscriber 所以 Observable 被激活,然后內(nèi)部就會(huì)使用各種變量來保存資源,如果不 dispose 的話,這些資源就會(huì)一直被 keep,很容易造成內(nèi)存泄漏。
同時(shí)手動(dòng) dispose 又嫌麻煩,所以就有了 DisposeBag,當(dāng)這個(gè) Bag 被回收時(shí),Bag 里面的 subscription 會(huì)自動(dòng)被 dispose,相當(dāng)于從 MRC 變成了 ARC。
小結(jié)
RxSwift 如果概念上整理清楚了,會(huì)發(fā)現(xiàn)其實(shí)并不難,多從 Observable 的角度去思考問題,多想著轉(zhuǎn)換和組合,慢慢就會(huì)從命令式編程轉(zhuǎn)到聲明式編程,對(duì)于抽象能力和代碼的可讀性都會(huì)有提升。
--EOF--
轉(zhuǎn)自http://limboy.me/tech/2016/12/11/time-to-learn-rxswift.html
make the world a little better and easier