Link.

無病呻呤區

話說使用 Swift 開發 iOS 也快一年的時間
其中很多函數程式設計的概念越發掘就覺得越有意思
半個月前一時心血來潮就跟隔壁的 iOS 開發小夥伴說

不如我們來用 RxSwift 吧,感覺潮潮 der~

然後我就去桂林詐騙山水甲天下旅遊玩耍了

沒想到一回來發現公司專案竟然已經全面引進 RxSwift!!

正是
雕闌玉砌應猶在,只是朱顏改。問君能有幾多愁,恰似一將春水向東流。

打開專案每個功能都很熟悉,但細看卻像是不同的語言一樣…
這種感覺就像看著已經進化的神奇寶貝,已經不是我的皮卡丘了

只好四處爬文、問熟悉 RxJava 的 Android 同事,準備來個第一次使用 Rx 就上手
結果真理告訴我們,著急是不好的,兩天內果然撞得頭破血流
於是靜下心來,痛定思痛
一個禮拜後的現在,帶著滿滿的挫拆
終於有了一些心得,在此留下我對 RxSwift 的理解,如有誤解,還請不吝糾正


過程記錄

我的學習方法首先是 clone 官方 RxSwift,跟著 playground 的文件走
然後看了十幾種創建 Observable 的方法覺得眼花撩亂
一知半解下帶著一堆新名詞看官方 Example,結果被上百個封裝方法嚇得差點閃尿
於是劍走偏鋒,開始 Google 看大量的賴人包,內容大約是 9 成應用, 1 成官方原始碼解析
我一向信奉量變造成質變,如果一篇文章看不懂,就多看幾篇吧。

我建議的學習方式是:

  • 明白引進 RxSwift 的目的:讓程式更清楚,更容易測試
  • 搞懂 RxSwift 核心觀念:函數響應式編程 + 觀察者模式
  • RxSwift 是 OpenSource,跟著 playground 走一遍,每個方法使用前先點進去看一下,弄清楚每個角色的職責
  • 看一百個範例不如實際動手做一個,直接拿一個舊專案出來,把 RxSwift 套進去吧,接著你才會遇到很多在看程式時不會遇到的問題,那就是真的需要搞懂的地方
  • 很多人會忽略官方 GitHub About Rx 那裡有一堆的觀念和教學文章,有時間的話建議看一遍,雖然我只看了不到一半

為什麼要用 RxSwift ?

Rx 是一門需要花時間體力去學習的科目
請當作不一樣的語言來學習
否則對角色的定位認知不清楚
直接看應用只是知其然卻不知其所以然
看到變數就先.看看會不會帶出什麼神奇的東西,那這條路將會非常坎坷

那我們就得評估為什麼要使用 RxSwift 了 ,畢竟前面說了這門科目的學習成本並不低
建議看看底部參考連結中作者寫的 Why Use Rx?,現在分享的是我的個人看法

首先我們來回想一下小時候一直在學的物件導向(Object-oriented programming)
OO 三大特性背得滾瓜爛熟
封裝(Encapsulation)、繼承(Inheritance)、多型(Polymorphism)
拆出了大量的 classmodelview

再加上 iOS 每個元件各種 delegateblock,有官方的、有自己寫的、有第三方的
每加一個,就像在畫紙上多畫一筆
彼此穿針引線,參數丟來丟去,代理時時換人做,追本溯源找到原 function 到底是哪個爹娘生出來的,看到 function 了,block 又是從哪丟進來的? 找個功能像在玩推理遊戲一樣

我常跟同事告解,我覺得我剛就業前三年寫的程式根本不能維護,在心理為那些前老闆們跟後進新人說聲抱歉,不過我們每次換公司也很常在做打掉重練這件事,大家都是工程師,業障輪流解,好像也是剛好而已

總之這些年的心得就是:

物件導向在開發上很彈性,隨心所欲,可以快速實現出我要的功能,但如果沒有好的策略架構,隨著專案的增長,維護上的體力是呈指數成長,導致小夥伴們都只想做新功能,沒人想修舊程式,人人視 fix issue 為洪水猛獸

那麼用了 RxSwift 又可以幫助多少呢?

先了解 RxSwift 在幹麻 ?

RxSwift 就是函數響應式編程(Functional Reactive Programming) + 觀察者模式(Observer Pattern)
Functional Reactive Programming 簡稱 FRP,是 Functional Programming + Reactive

那什麼是 Functional Programming ?

Functional Programming 是一種 CodeStyle
function 我都們很熟,小時候學的的直線方程式 f(x) = ax + b 就是 function,
直線為 y = 2x + b ,寫成程式就是

let a = 2
let b = 3
func getY(x: Int) -> Int {
    return ax + b
}
我們的程式生涯中這是再平常不過的一個方法
特別的是 Functional Programming CodeStyle 要注意
盡量做到
避免使用會變動的程式狀態及外部的可變變數
也就是剛剛的直線方程式參數 ab,不能為 var,也不會根據程式狀態 + 就變成 -
確保每次傳送相同的參數得到的 output 都是一樣,這樣也更適合做 Unit Test

回到 Functional Reactive Programming
也就是使用 Functional Programming 做非同步操作
像是耗時的資料在背景處理,之後再做 UI 更新

Functional Programming 這種思考方式需要花點時間習慣,接著來比較一下兩者的差異

差別比較


現在情境為
使用者登入後得到 UserModel 除了名字外還要判斷年紀,滿 18 歲顯示成年人介面,否則顯示未成年介面

class UserModel: NSObject {
    static let sharedInstance = UserModel()

    var name: String = "Guest"
    var age: Int = 0
}

接著登入從伺服器拿到 UserModel,這裡使用 block 來處理異步操作

API.login(account, pwd: password) { (userModel) in
    self.updateUI(userModel)
}

拿到使用者資料後更新 UI

func updateUI(userModel: UserModel) {
    userName.setText(userModel.name)
    if userModel.age >= 18 {
        setAdultUI()
    } else {
        setTeenageUI()
    }
}

RxSwift 的世界
我們會有一個存放使用者資料的 Observable,為了簡化示範,這裡就先放在 API 裡

let userVariable = Variable(UserModel())

登入時我們不需要 callback closure 了,在 API 取得資料時直接賦值給 userVariable

API.login(account, pwd: password)

在需要知道使用者資料的頁面

API.userVariable.asObservable()
                .subscribeNext { (userModel) in
                    userName.setText(userModel.name)
                    if userModel.age >= 18 {
                        setAdultUI()
                    } else {
                        setTeenageUI()
                    }
                }
                .addDisposableTo(disposeBag)

這樣子是使用 RxSwift 了,但我認為用 FRP 的思路來想應該多一個 map 來轉換出狀態,我也還在練習這種 CodeStyle,一樣我想歪了麻煩不吝糾正,有任何想法也歡迎提出討論

API.userVariable.asObservable()
                .map { (userModel: UserModel) -> (UserModel, Bool) in
                    return (userModel, (userModel.age >= 18))
                }
                .subscribeNext { (usrModel, isAdult) in
                    setAdultUI(isAdult)
                }
                .addDisposableTo(disposeBag)

可以很明顯看到差異
代碼更集中了,不用鑽到 updateUI 就能看到我們針對年齡是否滿 18 歲做了判斷,這點在有 delegate 時會更明顯,像如果我們要在使用者更改名稱時同步更改 UI 顯示,以往必須實做 UITextFieldDelegate

如果要在編輯名稱時同步修改 userModel 呢?
那這段操作又會散落一份在 textField delegate,在 shouldChange 得到輸入的值,但在 RxSwift 只需要多 bind 一個 rx_text,就可以在輸入文字時同步發送 onNext


參考連結

RxSwift GitHub [here]
Why use Rx [here]
Wiki Functional Programming 定義 [here]
Wiki Functional Reactive Programming 定義 [here]
淺入淺出 RxSwift & MVP 系列 Part 2 [here]
淺入淺出 RxSwift & MVP 系列 Part 3 [here]

CATEGORIES