Link.

前情提要

物件導向是厲害的心法,可以讓招式變得靈活,做到降低代碼耦合,讓招式跟招式間可以單獨存在、也可以互相組裝湊成一個套路

但套路一多,這些靈活反而讓整體變得複雜,今天某個動作會造成缺失讓敵人有機可趁
而我們想要修改掉這個缺陷,光是重構跟理解這些套路就要花許多功夫,重點是做起來會非常不開心

所以這幾年來我學設計模式、調整程式架構從 MVCMVP,到現在引入 RxSwift 都是為了同一個目標

讓程式能更加清楚,更方便測試
以前我的專案要更改一個功能假設時間是十分鐘,在一年後改相同的功能可能卻要花兩個小時去研究跟回想,希望能夠做到讓專案不管放多久甚致換了一個工程師,都能清楚掌握其脈絡

接下來正式進入 RxSwift 的世界

首先我們要明白 RxSwift 是使用觀察者模式來傳送事件

在 iOS Foudation 裡封裝了一個物件 NSNotificationCenter 就是觀察者模式
除了自定義 function,也可以監聽系統事件,像觀察UIKeyboardWillShowNotificationUIKeyboardWillHideNotification 做到在鍵盤出現時調整 UI

我認為觀察者模式在職責上區分更清楚,資料層就不需要管其它地方需要做什麼事

我這裡由圖表比較在戰場上敵軍來襲這種狀況發生時,使用代理和觀察者模式的兩種實踐方式
想更清楚比較差異,可以看參考連結作者的 Why Use Rx?,裡頭比較了 DelegateKVO 等跟 Rx 的差別

使用代理處理的話,在觀測所必須要準備所有需要知道敵軍來襲這件事的地區信使(delegate),再由 delegate 來呼叫執行對應的處理

在觀察者模式中,觀測所只需要照顧好自己的任務:發現敵軍來襲並且點燃信號,不必理會後續其他地方對應的處理方式

RxSwift 實踐觀察者模式主要需要三個角色

Observable
即可被觀察的對象,可以想做是戰場上的烽火台,前方戰事有變化,可以即刻點燃信號,而且這信號很厲害還可以夾帶任何想要傳送的訊息

至於後方看到信號要做什麼事,就不是峰火台可以干涉的了,Observable 裡面可以放變數、Model、或任何想要被觀察的物件
observer
被指派去烽火台值班的人,負責點燃信號火焰,每個 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)
    其實都是同一條路,發起通知,只是點燃不同的信號火焰,其中 onNextonError 都可以夾帶進一步的信號內容
明白了角色定位,那麼要實作一個完整的 RxSwift 觀察者模式 自然就是建立這三個角色

第一步:建設烽火台 (Create Observable)

RxSwift 的原始碼 playground 中 Creating and Subscribing to ObservablesWorking 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 的變化,只要訂閱發送方就可以了
現在指揮所要建立一個觀測烽火台警報的任務

第三步:主陣營的觀察任務(Subscribe)

前線.烽火台.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(),就是立刻丟掉,即刻取消這個觀測任務

初期在看範例時看到用法務必點進去了解是哪個身份在做這件事
.mapObservableType 在做的,回傳也是一個 ObservableType
這代表我們可以.map 後再 .map ,連續轉換訊號資料

.subscribeNext 回傳是 Disposable,這是一個任務的設定完成,所以.map 不能接在 .subscribeNext
.subscribeNext後就不能再接.subscribeError,想要同時觀察 onNextonCompleteonError
請使用 subscribe(on: (event: RxSwift.Event) -> Void)

關於更多名詞介紹

Subject

Subject 是封裝過的 Observable,並且實作了 ObserverType,所以可以當作內建一個觀察人員的觀測所,而且可以遠距離指示直接對該對象下指令
而前面提到的Observable.create()只能在創建之初給與觀測任務,由觀測人員依照任務內容來判斷發送信號,但 Subject 因為實作了 ObserverType,所以可以直接 .onNext,如果對象是 Observerble,那是 . 不出來的

RxSwift Playground 文件中介紹了三種 Subject 有類型
- PublishSubject
- ReplaySubject
- BehaviorSubject

隨便挑一個 Subject 看程式碼可以看到 Subject onEventNSRecursiveLock 做了同步處理,避免第一個信號還沒發送完就又要發送第二個信號

又分別依不同 Subject 的特性做進一步加強
BehaviorSubjectPublishSubject 在訂閱的地方有也很明顯的不同,我們可以來比較一下
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 自動發送 onNextBehaviorSubject,使用上非常方便
就不用在資料更改時還要另外運行 .onNext(value)

非常建議初學者不要怕麻煩,多追蹤原始碼,除了觀摩大神們的超凡技藝,也能在過程中對 RxSwift 有更深入的體會

side-effect
這是我在看文件時很常出現的單字,直翻中文是副作用,單看字面意思會覺得一頭霧水
在程式領域 function 執行過程除掉會影響輸入輸出數據的,就是 side-effect,像是外部變數、UI 操作等等,而有 side-effect 的函數,稱為非純函數(Impure Function),反之則稱為純函數(Pure Function)

以上是 RxSwift 的基礎概念,完全理解後就能開始四處看江湖偏方網路範例學習了

務必下載 RxSwift Project,除了範例外也可以看到全部的封裝代碼,RxSwift 更進一步變化應用大都集中在 ObservableRxCocoa

針對 Observable 的一些封裝處理可以看參考連結的 RxMarbles 網站

針對 Foundation 的物件像 UILabel、UITable etc, 這些放在 RxCocoa,都是對 iOS Cocoa 原生物件一些數值的封裝,一層一層追蹤進去,務必搞清楚每個身分的定位,就會發現 Rx 的世界裡各司其職,其實非常有秩序


參考連結

RxSwift GitHub [here]
Why use Rx [here]
RxMarbles [here]
Wiki Observer pattern 定義 [here]
淺入淺出 RxSwift & MVP 系列 Part 1 [here]
淺入淺出 RxSwift & MVP 系列 Part 3 [here]

CATEGORIES