物件導向是厲害的心法,可以讓招式變得靈活,做到降低代碼耦合,讓招式跟招式間可以單獨存在、也可以互相組裝湊成一個套路
但套路一多,這些靈活反而讓整體變得複雜,今天某個動作會造成缺失讓敵人有機可趁
而我們想要修改掉這個缺陷,光是重構跟理解這些套路就要花許多功夫,重點是做起來會非常不開心
所以這幾年來我學設計模式、調整程式架構從 MVC 到 MVP,到現在引入 RxSwift 都是為了同一個目標
讓程式能更加清楚,更方便測試
以前我的專案要更改一個功能假設時間是十分鐘,在一年後改相同的功能可能卻要花兩個小時去研究跟回想,希望能夠做到讓專案不管放多久甚致換了一個工程師,都能清楚掌握其脈絡
首先我們要明白 RxSwift 是使用觀察者模式來傳送事件
在 iOS Foudation 裡封裝了一個物件 NSNotificationCenter 就是觀察者模式
除了自定義 function,也可以監聽系統事件,像觀察UIKeyboardWillShowNotification、UIKeyboardWillHideNotification 做到在鍵盤出現時調整 UI
我認為觀察者模式在職責上區分更清楚,資料層就不需要管其它地方需要做什麼事
我這裡由圖表比較在戰場上敵軍來襲這種狀況發生時,使用代理和觀察者模式的兩種實踐方式
想更清楚比較差異,可以看參考連結作者的 Why Use Rx?,裡頭比較了 Delegate、KVO 等跟 Rx 的差別
使用代理處理的話,在觀測所必須要準備所有需要知道敵軍來襲這件事的地區信使(delegate),再由 delegate 來呼叫執行對應的處理
在觀察者模式中,觀測所只需要照顧好自己的任務:發現敵軍來襲並且點燃信號,不必理會後續其他地方對應的處理方式
即可被觀察的對象,可以想做是戰場上的烽火台,前方戰事有變化,可以即刻點燃信號,而且這信號很厲害還可以夾帶任何想要傳送的訊息
至於後方看到信號要做什麼事,就不是峰火台可以干涉的了,Observable 裡面可以放變數、Model、或任何想要被觀察的物件
被指派去烽火台值班的人,負責點燃信號火焰,每個 observer 要 implement ObserverType Protocol,這個 protocol 只有一個任務 func on(event: Event<E>)
就是發起通知事件
<Observable>
.subscibe被指派去觀察烽火台是否有異狀的人,可以指示不同觀察者根據傳遞出的信號訊息做出不同的反應
從 on(event: Event) 追蹤下去可以看到
Observerbale 可以發送的 Event 有三種
- case Next(Element) /// Next element is produced.
- case Error(ErrorType) /// Sequence terminated with an error.
- case Completed /// Sequence completed successfully.
ObserverType Extension 進一步封裝出
- final func onNext(element: E)
- final func onCompleted()
- final func onError(error: ErrorType)
其實都是同一條路,發起通知,只是點燃不同的信號火焰,其中 onNext 跟 onError 都可以夾帶進一步的信號內容
RxSwift 的原始碼 playground 中 Creating and Subscribing to Observables 和 Working with Subjects 這兩個章節都是在講這一個步驟,裡頭十幾二十個 Method,都是在建 Observable
舉例使用 Observable.create()
,這是一個常用的建立 Observable 方法
原始碼可以看到是 Observable 的 function
extension Observable {
public static func create(subscribe: (AnyObserver<E>) -> Disposable) -> Observable<E> {
return AnonymousObservable(subscribe)
}
}
<ObservableType>
)Observable.create()
這個 function closure 裡已經內建了一個值班人員 observer,但他還沒有被指派觀測任務
假設這個烽火台被蓋在前線,烽火台只有一個發送任務,就是有敵軍出現時要發送數量的情報
至於情報發去哪?有沒有人接到?就不是這區區一個觀測士兵可以插手的事情了
let 烽火台 = Observable.create { observer in
if (敵軍出現) {
observer.on(.Next(敵軍數量))
}
return NopDisposable.instance
}
發起信號的角色設定好了,再來就是接收方的角色
任何地方都可以決定要不要接收 Observable 的變化,只要訂閱發送方就可以了
現在指揮所要建立一個觀測烽火台警報的任務
前線.烽火台.subscribeNext { (敵軍數量) in
if 敵軍數量 很多 {
求援
} else {
出兵抵擋
}
}
.addDisposableTo(disposeBag)
至止,為一個完整的 RxSwift 觀察者模式 實踐,我們的敵軍觀測任務已經完成可以運作
另外一個發送方可以有很多個接收方來觀察,即一個前線觀測站發現敵軍,可以多方出兵增援,這是非常合理的
程式碼看到這裡會發現 Observable.create()
中 observer 會回傳 Disposable
<Observable>
.subscribe 也會回傳 Disposable
每個發送任務和觀察任務都會回傳的這個型態:Disposable 是一個 Protocol,追蹤過去會看到實現了一個 function dispose(),就是中止任務
註:有玩 RxJava 的同學會發現在 RxJava 裡每個觀測任務的回傳是叫 subscription,搜尋 RxSwift 的 RxExmpale 可以發現很多地方也是將回傳的 Disposable 命名為 subscription,兩者是相同的東西
接著,可以看到觀測任務最後有個.addDisposableTo(disposeBag)
這就像 iOS 使用 Notification 時有增加就要有移除
NSNotificationCenter.defaultCenter().addObserver
NSNotificationCenter.defaultCenter().removeObserver
rxSwfit 當然也是,在撤離戰場時,總要將觀察人員帶走
很常看到在 .subscribe
後會接 .addDisposableTo(disposeBag)
DisposeBag 點進去可以看到一個存放 Disposable 的陣列,所以就想成一個裝 disposable 的袋子,在頁面回收時整袋再一起丟掉
而 Disposable.dispose()
,就是立刻丟掉,即刻取消這個觀測任務
初期在看範例時看到用法務必點進去了解是哪個身份在做這件事
像 .map
是 ObservableType 在做的,回傳也是一個 ObservableType
這代表我們可以.map
後再 .map
,連續轉換訊號資料
.subscribeNext
回傳是 Disposable,這是一個任務的設定完成,所以.map
不能接在 .subscribeNext
後
而.subscribeNext
後就不能再接.subscribeError
,想要同時觀察 onNext 跟 onComplete、onError
請使用 subscribe(on: (event: RxSwift.Event
Subject 是封裝過的 Observable,並且實作了 ObserverType,所以可以當作內建一個觀察人員的觀測所,而且可以遠距離指示直接對該對象下指令
而前面提到的Observable.create()
只能在創建之初給與觀測任務,由觀測人員依照任務內容來判斷發送信號,但 Subject 因為實作了 ObserverType,所以可以直接 .onNext
,如果對象是 Observerble,那是 . 不出來的
RxSwift Playground 文件中介紹了三種 Subject 有類型
- PublishSubject
- ReplaySubject
- BehaviorSubject
隨便挑一個 Subject 看程式碼可以看到 Subject 對 onEvent 用 NSRecursiveLock 做了同步處理,避免第一個信號還沒發送完就又要發送第二個信號
又分別依不同 Subject 的特性做進一步加強
BehaviorSubject 跟 PublishSubject 在訂閱的地方有也很明顯的不同,我們可以來比較一下
BehaviorSubject 被訂閱時可以明顯看到同步執行了 onNext
let key = _observers.insert(observer.asObserver())
observer.on(.Next(_value))
return SubscriptionDisposable(owner: self, key: key)
一樣的地方場景轉到 PublishSubject 就只有訂閱而已
let key = _observers.insert(observer.asObserver())
return SubscriptionDisposable(owner: self, key: key)
可以得知 PublishSubject 訂閱時拿不到當前的觀察物件,只有在下次 onNext
時會拿到
而 BehaviorSubject 則在一訂閱時就會拿到當前物件
ReplaySubject 比較複雜,多了 bufferSize 可以運用
大概看一下它做了什麼事,在 onNext
時多了 addValueToBuffer(value)
將 value 加進 buffer
在被訂閱時 replayBuffer(AnyObserver)
持續一路追蹤下次可以推論出根據 ReplaySubject 會在新的訂閱者訂閱時根據 buffer 大小補發過往的事件
再來看另一個很常用的方法 Variable(Element)
這是一個對 Subject 的封裝,裡面內建了一個 BehaviorSubject,
public var value: E {
get {
_lock.lock(); defer { _lock.unlock() }
return _value
}
set(newValue) {
_lock.lock()
_value = newValue
_lock.unlock()
_subject.on(.Next(newValue))
}
}
看過 Subject 的程式碼再來看 Variable
其實 Variable 就是一個會在 value 自動發送 onNext
的 BehaviorSubject,使用上非常方便
就不用在資料更改時還要另外運行 .onNext(value)
非常建議初學者不要怕麻煩,多追蹤原始碼,除了觀摩大神們的超凡技藝,也能在過程中對 RxSwift 有更深入的體會
side-effect
這是我在看文件時很常出現的單字,直翻中文是副作用,單看字面意思會覺得一頭霧水
在程式領域 function 執行過程除掉會影響輸入輸出數據的,就是 side-effect,像是外部變數、UI 操作等等,而有 side-effect 的函數,稱為非純函數(Impure Function),反之則稱為純函數(Pure Function)
以上是 RxSwift 的基礎概念,完全理解後就能開始四處看
江湖偏方網路範例學習了務必下載 RxSwift Project,除了範例外也可以看到全部的封裝代碼,RxSwift 更進一步變化應用大都集中在 Observable 跟 RxCocoa
針對 Observable 的一些封裝處理可以看參考連結的 RxMarbles 網站
針對 Foundation 的物件像 UILabel、UITable etc, 這些放在 RxCocoa,都是對 iOS Cocoa 原生物件一些數值的封裝,一層一層追蹤進去,務必搞清楚每個身分的定位,就會發現 Rx 的世界裡各司其職,其實非常有秩序
參考連結
RxSwift GitHub [here]