概述
很多小伙伴在初學(xué) RxSwift 時(shí)嚷兔,在面對(duì)大量的操作符和各種抽象的概念時(shí)可能都會(huì)感到無從下手盹愚,但其實(shí)官方提供的 Example 就能夠很方便的幫助我們學(xué)習(xí) RxSwift 的各種概念以及如何與 MVVM 相結(jié)合师崎。畢竟直接看實(shí)際的運(yùn)用場景比看抽象的概念要容易理解的多咐柜。
我個(gè)人認(rèn)為 RxSwift 的核心其實(shí)很簡單师骗,就是把要操作的事件黔龟、屬性都抽象成一個(gè)個(gè)Observable
妇智,剩下的變換、組合氏身、過濾巍棱、響應(yīng)(訂閱)、線程調(diào)度蛋欣、生命周期管理都是你對(duì) Observable
的加工和處理航徙。如果你真的理解了什么是 Observable
,哪些東西可以被包裝成 Observable
那么你已經(jīng)成功了一半啦陷虎,就像你剛學(xué)面向?qū)ο缶幊痰教ぃ愣耸裁词菍?duì)象,哪些東西可以被抽象成對(duì)象以后泻红,后面的學(xué)習(xí)是不是順利了很多呢夭禽。
其實(shí)初學(xué)的時(shí)候真的不用在面對(duì)大量的操作符時(shí)困惑,也不要有怕難的心態(tài)谊路,覺得這么多東西我得記到什么時(shí)候讹躯。可以先看一遍文檔有能力的也可以看看源碼缠劝。用的多了你會(huì)發(fā)現(xiàn)和我們平常寫代碼一樣潮梯,常用的 Api 就那些,其他用的少的真忘記的時(shí)候再翻翻文檔就好惨恭。(中文文檔 RxSwift-Chinese-Documentation)
好了廢話不多說秉馏,本篇所有示例代碼在 RxSwift 官方項(xiàng)目中就有,下載完了直接運(yùn)行即可脱羡。
可以看到官方為我們分了三塊內(nèi)容萝究,
- iPhone Example
- TableView 和 CollectionView Example
- 綜合的 Example
本篇文章我們先從這些 Demo 里最簡單的 Bindings 說起。
Adding numbers
先看運(yùn)行效果有三個(gè) UITextField
任何一個(gè) UITextField
輸入內(nèi)容時(shí)锉罐,把三者相加帆竹,在最底部 UILabel
顯示結(jié)果。
這是一個(gè)很簡單的響應(yīng)式編程的例子脓规,這個(gè)頁面主要由四個(gè)元素組成
@IBOutlet weak var number1: UITextField!
@IBOutlet weak var number2: UITextField!
@IBOutlet weak var number3: UITextField!
@IBOutlet weak var result: UILabel!
先考慮一下以往我們會(huì)怎么實(shí)現(xiàn):
- 監(jiān)聽三個(gè)
UITextField
的文本框改變(Delegate
栽连、KVO
、NSNotificationCenter
、addTarget
)秒紧。 - 在回調(diào)方法中將三個(gè)
UITextField
輸入內(nèi)容轉(zhuǎn)成Int
相加并賦值給UILabel
绢陌。
原生實(shí)現(xiàn)最大的問題在于,無論哪種方式熔恢,監(jiān)聽文本框內(nèi)容改變的代碼都不是那么的優(yōu)雅脐湾。
看一下用 RxSwift 如何實(shí)現(xiàn)〖ㄆ福回想我們剛剛所說的核心沥割,把屬性或者事件抽象成 Observable
,再對(duì) Observable
進(jìn)行操作凿菩。
- 包裝 (把三個(gè)
UITextField
輸入的內(nèi)容抽象成三個(gè)Observable
)
number1.rx.text.orEmpty
number2.rx.text.orEmpty
number3.rx.text.orEmpty
- 組合 (把三個(gè)
Observable
組合,并將每一個(gè)值轉(zhuǎn)成Int
類型相加)帜讲。
Observable.combineLatest(number1.rx.text.orEmpty, number2.rx.text.orEmpty, number3.rx.text.orEmpty) { textValue1, textValue2, textValue3 -> Int in
return (Int(textValue1) ?? 0) + (Int(textValue2) ?? 0) + (Int(textValue3) ?? 0)
}
文檔中為我們描述的 combineLatest
的作用衅谷。
當(dāng)多個(gè) Observables 中任何一個(gè)發(fā)出一個(gè)元素,就發(fā)出一個(gè)元素似将。這個(gè)元素是由這些 Observables 中最新的元素获黔,通過一個(gè)函數(shù)組合起來的。
說的再直白一點(diǎn)就是在验,這三個(gè) UITextField
任意一個(gè)有新值的時(shí)候都會(huì)被組合成新的Observable
玷氏。
- 轉(zhuǎn)換 (把
Int
類型轉(zhuǎn)換成String
類型)
.map { $0.description }
- 響應(yīng) (把值綁定到
UILabel
)
.bind(to: result.rx.text)
- 管理生命周期
.disposed(by: disposeBag)
怎么樣是不是很簡單呢,只需要6行代碼我們就完成了這個(gè)需求腋舌。
SimpleValidation
運(yùn)行效果
- 當(dāng)用戶輸入用戶名時(shí)盏触,如果用戶名不足 5 個(gè)字就顯示紅色提示語,并且無法輸入密碼块饺,當(dāng)用戶名符合要求時(shí)才可以輸入密碼赞辩。
- 同樣的當(dāng)用戶輸入的密碼不到 5 個(gè)字時(shí)也顯示紅色提示語。
- 當(dāng)用戶名和密碼有一個(gè)不符合要求時(shí)底部的綠色按鈕不可點(diǎn)擊授艰,只有當(dāng)用戶名和密碼同時(shí)有效時(shí)按鈕才可點(diǎn)擊辨嗽。
- 當(dāng)點(diǎn)擊綠色按鈕后彈出一個(gè)提示框
頁面由以下元素組成
/// 用戶名
@IBOutlet weak var usernameOutlet: UITextField!
/// 用戶名紅色提示語
@IBOutlet weak var usernameValidOutlet: UILabel!
/// 密碼
@IBOutlet weak var passwordOutlet: UITextField!
/// 密碼紅色提示語
@IBOutlet weak var passwordValidOutlet: UILabel!
/// 按鈕
@IBOutlet weak var doSomethingOutlet: UIButton!
還是先考慮一下以往我們會(huì)怎么實(shí)現(xiàn):
- 監(jiān)聽兩個(gè)
UITextField
的文本框改變(Delegate
、KVO
淮腾、NSNotificationCenter
糟需、addTarget
)。 - 在回調(diào)方法中判斷用戶名是否符合要求谷朝,用判斷結(jié)果去設(shè)置提示語是否隱藏和密碼框是否可以輸入洲押。
- 在回調(diào)方法中判斷密碼是否符合要求,用判斷結(jié)果去設(shè)置提示語是否隱藏徘禁。
- 拿到前兩個(gè)判斷的結(jié)果诅诱,當(dāng)前兩個(gè)的結(jié)果都為真時(shí),按鈕可以點(diǎn)擊送朱。
- 添加按鈕點(diǎn)擊事件娘荡。
看一下用 RxSwift 如何實(shí)現(xiàn)干旁。還是我們剛剛所說的核心,把屬性或者事件抽象成 Observable
炮沐,再對(duì) Observable
進(jìn)行操作争群。
- 用戶名格式是否正確 (變換)
let usernameValid = usernameOutlet.rx.text.orEmpty
.map { $0.count >= minimalUsernameLength }
.share(replay: 1)
- 密碼格式是否正確 (變換)
let passwordValid = passwordOutlet.rx.text.orEmpty
.map { $0.count >= minimalPasswordLength }
.share(replay: 1)
- 兩者都正確(組合 ,
combineLatest
作用上面介紹過大年,再鞏固一下)
let everythingValid = Observable.combineLatest(usernameValid, passwordValid) { $0 && $1 }
.share(replay: 1)
這里你可能比較疑惑這個(gè) .share(replay: 1)
是干嘛的换薄。
通俗一點(diǎn)說,當(dāng)有多個(gè)訂閱者去訂閱同一個(gè) Observable
的時(shí)候翔试,我們不希望 Observable
每次有新的訂閱者都去執(zhí)行轻要。
- 響應(yīng)
usernameValid
.bind(to: passwordOutlet.rx.isEnabled)
.disposed(by: disposeBag)
usernameValid
.bind(to: usernameValidOutlet.rx.isHidden)
.disposed(by: disposeBag)
passwordValid
.bind(to: passwordValidOutlet.rx.isHidden)
.disposed(by: disposeBag)
everythingValid
.bind(to: doSomethingOutlet.rx.isEnabled)
.disposed(by: disposeBag)
由于 .share(replay: 1)
的存在,這個(gè)判斷密碼或者用戶名是否合法的操作垦缅,不管以后被多少人訂閱冲泥,只會(huì)判斷一次,這樣可以避免不必要的資源消耗壁涎。
- 按鈕添加點(diǎn)擊事件
doSomethingOutlet.rx.tap
.subscribe(onNext: { [weak self] _ in self?.showAlert() })
.disposed(by: disposeBag)
當(dāng)然這步我們是可以通過擴(kuò)展的方式讓他更加優(yōu)雅的凡恍,這篇我們不做過多講解。
最后說句題外話怔球,學(xué)東西還是要從簡單的學(xué)起嚼酝,這樣會(huì)多一些正面積極的反饋,就像是打游戲總會(huì)一段時(shí)間就有一個(gè)小的獎(jiǎng)勵(lì)竟坛,刺激你繼續(xù)玩下去闽巩。如果一上來就看大量的操作符,各種概念和源碼流码,挫敗感一大就不想學(xué)了又官。
下一篇將更新如何與 MVVM 結(jié)合做一個(gè) GitHub
的注冊(cè)界面。