Table of contents


一般處理 collection 都是用 Array 的方式,而RxSwift則是使用 Observable ,以event base的方式逐次送出 array的內容

[1, 2, 3, 4, 5].foreach 
[1, 2, 3, 4, 5].subscribe()
  • next : 每次的event會依續收到 1, 2, 3, 4, 5
  • error : 如果發生異常時,會收到error,ex: 1, 2, 3, X 原本應該收到1, 2, 3, 4, 5,在3後收到異常X,之後就停止了
  • completed : 1, 2, 3, 4, 5後,最後會送一個完成的訊號代表已經全部發送成功

所以,rxswift的基本操作就是 發送 -> 操作 -> 接收,然後,每次處理一個單位

  • 錯誤訊息用just
  • 在class 用一個 DisposeBag蒐集所有disposiables
  • 在unit test裡的observable 不會被觸發,但如果程式用debugging mode的step run又會更常被觸發,可能是thread引起的問題,目前最簡單的解法是在unit test裡引都RxBlocking
  • Sequence 在 subscribe 被調用後才會開始產生
  • RxCocoa對UIKit Framework有做extension,method以rx_前綴,使用這些 extension 開發的方式會跟傳統的 iOS 方式不太一樣,rx_前綴的 function取得的結果會比較方便跟rx_swift做整合
  • Delegate一次只能binding到一個Observer,如需binding到多個Observers需要用DelegateProxy

Multiple output

雖然 Swfit 2 有提供 Exception Handling,但是這個在 Function Programming 裡並不好使, 因為它是透過中斷整個處理流程的方式處理 exception,但在 Function Programming 應該是要 一個操作串接著一個操作一直往下做的,所以,在Function Programming的異常處理通常是透過下面的方式

function programming 的基本流程是 IPO (Input -> Process -> Output), 如果需要多種輸出的結果,可以用以下這個 (Monad), 輸出可以有兩種(或多種)

type Result =
    | Success
    | Failure
  • Either<A, B> type
  • Result<Success,Failure>
  • Optional<Some, None>

經過這樣子的處理後,對於 exception flow的處理就變得很直觀,像這樣

type LogingResult = 
    | User Not Exist  
    | Password not confirmed
    | Password not match
    | Account is disabled
    | ...
    | Login Success

一般如果是用 imperative language (指令式語法),就是一堆 if / else, 或 try / catch的流程處理, 而在 functional language 就是一個function串著一個function的方式一直寫下來(Pipeline),最後進入 happy path(成功)

關於這樣的寫法,強列建議仔細看過Railway oriented programming的文章及影片

在RxSwift裡的結果,就是分為

  1. onNext 成功
  2. onError 失敗
  3. onCompleted 完成: 完成後就不可再發送任何訊息
  4. onDispose 解構: 資源釋放

DelegateProxy

  • proxy 負責將iOS的delegate轉成RxSwift可用的格式
  • 在原來的View(不是ViewController)透過extension的方式建立 delegateProxy

RxCocoa units

在Cocoa調用Observable需要注意幾件事:

  • 不能輸出error,因為無法直接binding error到元件上
  • observer 要在main scheduler,UI只能在main thread上操作
  • subscribe 要在main scheduler,UI只能在main thread上操作
  • 需要共用event的 side effect, 預設每個 subscriber收到的事件是獨立的,但ui上常需共享 http reuqest的結果,不然就得多次調用 http request,所以共用 http request的結果(http response)是比較有效率的作法

像上面這些使用上的限制,可以自已寫code處理,像這樣

let results = query.rx_text
    .throttle(0.3, scheduler: MainScheduler.instance)
    .flatMapLatest { query in
        fetchAutoCompleteItems(query)
            .observeOn(MainScheduler.instance)       // observer 要在main scheduler
            .catchErrorJustReturn([])                // 不能輸出errorhandled
    }
    .shareReplay(1)                                  // 需要共用event的 side effect
                                                     

results
    .map { "\($0.count)" }
    .bindTo(resultCount.rx_text)
    .addDisposableTo(disposeBag)

results
    .bindTo(resultTableView.rx_itemsWithCellIdentifier("Cell")) { (_, result, cell) in
        cell.textLabel?.text = "\(result)"
    }
    .addDisposableTo(disposeBag)

但如果用Unit的設計,RxCocoa就會自動處理,可以省去不少code

let results = query.rx_text.asDriver()        // 轉成 Driver Unit
    .throttle(0.3, scheduler: MainScheduler.instance)
    .flatMapLatest { query in
        fetchAutoCompleteItems(query)
            .asDriver(onErrorJustReturn: [])  // 錯誤訊息處理
    }

results
    .map { "\($0.count)" }
    .drive(resultCount.rx_text)               // 如果用了driver,bindTo要改成driver
    .addDisposableTo(disposeBag)              
                                              
results
    .drive(resultTableView.rx_itemsWithCellIdentifier("Cell")) { (_, result, cell) in  // 如果用了driver,bindTo要改成driver
        cell.textLabel?.text = "\(result)"
    }
    .addDisposableTo(disposeBag)

其他類似的Unit有: Driver,ControlProperty,ControlEvent,Variable,Unit是可選的,使用Unit可以讓程式的意圖更清晰

Driver unit

  • can’t error out
  • observe on main scheduler
  • sharing side effects (shareReplayLatestWhileConnected)

ControlProperty / ControlEvent

  • can’t error out
  • subscribe on main scheduler
  • observe on main scheduler
  • sharing side effects

Variable

  • can’t error out
  • sharing side effects