話說使用 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 那裡有一堆的觀念和教學文章,有時間的話建議看一遍,雖然我只看了不到一半
Rx 是一門需要花時間體力去學習的科目
請當作不一樣的語言來學習
否則對角色的定位認知不清楚
直接看應用只是知其然卻不知其所以然
看到變數就先.看看會不會帶出什麼神奇的東西,那這條路將會非常坎坷
那我們就得評估為什麼要使用 RxSwift 了 ,畢竟前面說了這門科目的學習成本並不低
建議看看底部參考連結中作者寫的 Why Use Rx?,現在分享的是我的個人看法
首先我們來回想一下小時候一直在學的物件導向(Object-oriented programming)
OO 三大特性背得滾瓜爛熟
封裝(Encapsulation)、繼承(Inheritance)、多型(Polymorphism)
拆出了大量的 class、model、view
再加上 iOS 每個元件各種 delegate、block,有官方的、有自己寫的、有第三方的
每加一個,就像在畫紙上多畫一筆
彼此穿針引線,參數丟來丟去,代理時時換人做,追本溯源找到原 function 到底是哪個爹娘生出來的,看到 function 了,block 又是從哪丟進來的? 找個功能像在玩推理遊戲一樣
總之這些年的心得就是:
物件導向在開發上很彈性,隨心所欲,可以快速實現出我要的功能,但如果沒有好的策略架構,隨著專案的增長,維護上的體力是呈指數成長,導致小夥伴們都只想做新功能,沒人想修舊程式,人人視 fix issue 為洪水猛獸
那麼用了 RxSwift 又可以幫助多少呢?
RxSwift 就是函數響應式編程(Functional Reactive Programming) + 觀察者模式(Observer Pattern)
而 Functional Reactive Programming 簡稱 FRP,是 Functional Programming + Reactive
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
}
也就是剛剛的直線方程式參數 a 和 b,不能為 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]