作為一個RxSwift初學者,寫這篇文章是很惶恐的(其中借鑒了很多大神的博文,后面會給出鏈接),但是管中窺豹,我會詳解RxSwift官方Demo中的一段代碼,讓還沒有接觸RxSwift的小伙伴感受到RxSwift為什么會讓我們的開發(fā)變得簡單高效。
RxSwift到底是什么?
RxSwift是一種函數(shù)式響應式編程。那什么是函數(shù)式編程呢,函數(shù)式編程最重要的概念就是“無狀態(tài)(immutable)”,看到這有些小伙伴可能會很開心,無狀態(tài)(知名LOL職業(yè)選手)嘛,我是他的粉絲!言歸正傳,到底什么是“無狀態(tài)(immutable)”呢?我看了很多文章,但是都被他們專業(yè)的描述整的一頭霧水,我來說說我的看法:有豐富debug經(jīng)驗的小伙伴們都知道一個事實,程序中80%的BUG都可以由追蹤變量的值來發(fā)現(xiàn),為什么呢?反過來想,引起B(yǎng)UG的最大元兇正是值的改變。說道這里大家肯定都有了一個大膽的想法,倘若有一種方式能夠消除狀態(tài)的改變(這里為什么不說“值”,因為Rx不僅僅是值的“無狀態(tài)(immutable)”,也會有事件的“無狀態(tài)(immutable)”,所以準確的描述應該是“狀態(tài)”),那么豈不是就沒什么BUG了?函數(shù)式編程正是為此而生。
那函數(shù)式編程究竟是怎么做到“無狀態(tài)(immutable)”這一點的呢?答案就是“序列(sequence)”,既然我不能改變狀態(tài),那我需要發(fā)生改變的時候,我就重新生成一個狀態(tài),發(fā)生幾次改變,我就生成幾個狀態(tài),而“序列(sequence)”就是盛放這些狀態(tài)的容器(大家可以看做是一個數(shù)組),那么狀態(tài)的改變是不是就不復存在?答案是肯定的,我只需要分別對“序列(sequence)”中的狀態(tài)做出命令,他們之間就不會互相影響,那么出bug的幾率自然大大降低。
那么既然成為了一個“序列”,怎么生成“序列”,怎么組合多個“序列”,怎么處理每個“序列”中的每個元素?RxSwift中有非常多的操作符來解決這些問題,這也是RxSwift難以學習的原因之一。但是我們只要理解,這些操作符的的最終目標是讓“序列”變成我們想要的樣子。記憶起來就會事半功倍。
說完函數(shù)式,再來說說響應式編程,先看段代碼:
var a: Int = 0
var b: Int = 0
var c = a + b
a = 1
print(c)
盡管a在c打印之前賦值為1,但是在給c賦值的時候 a + b 還是等于 0 ,我們要想讓c得到最新的結果,就需要在c賦值之前將a的值改為1,那么如果a和b的值在之后的代碼中會一直變動,就需要不斷的給c寫一行重新賦值的代碼:c = a+ b,這真是太麻煩了,響應式就是為了解決這個麻煩誕生的,當a或者b發(fā)生變化的時候,c的值會響應a或者b的值發(fā)生變化,所以就被稱之為響應式了,那么響應式如果實現(xiàn)呢,最簡單容易理解的方式就是通過a或者b的屬性監(jiān)聽器,當監(jiān)聽到a或者b發(fā)生變化時,就重新給c賦值。實際上,無論代碼怎么寫,無非是在某一層隱式調(diào)用了c = a + b這一行代碼而已,即使我們直接去操作指針,也需要有一行代碼將兩個指針指向的值相加,所以響應式并不是難以理解的技術,但是響應式編程能夠極大的改善代碼的可讀性,更直觀,更容易維護。
RxSwift,就是將函數(shù)式,響應式,以及鏈式編程的結合體,即使在項目中由于各種原因不能去使用,讀懂它,也能讓你在寫業(yè)務代碼的時候更加游刃有余。
在這里再多說幾句:有很多博客把“序列(sequence)”比作“流、信號、管道”等,這確實有助于大家理解RxSwift,但是RxSwift的項目維護者顯然不這么認為,下面給大家看一段InfoQ對項目維護者Krunoslav Zaher的采訪:
使用不一樣的、不太準確的方式思考可觀察的序列會帶來很多問題(流、信號、導管等)。那些東西有時候可以用來幫助新手探索Rx,但是如果使用的時間過長,它們只會在接下來的開發(fā)中帶來很多困惑。人們有時候使用它們是因為Rx可以用在模擬系統(tǒng)的多狀態(tài)部分,但是那只是故事的一部分,而我建議大家不要那樣思考。
使用那些項(流、信號、導管等)思考的問題在于它們帶有一個隱含假定:
定義Observable時帶有共享的值,并且可以從外部設定的當前值也是一樣的。而Observable會被莫名其妙地直接取消,這看起來像future或promise。
而事實上:
Observable只是一個如何計算序列的定義。沒有計算會在序列定義后(let result = source.map(transformObservableSequence))自動地執(zhí)行。這和Swift中的SequenceSequence是一樣的。當綁定一個觀察者時(這與調(diào)用SequenceType中的generate方法相同)才會執(zhí)行計算,并且可以通過處理Disposable對象的結果來取消特定的計算。Observable當然可以代表多狀態(tài)的計算,它們共享潛在的綁定并source可觀察的序列(使用shareReplay(1)操作等),但是這不是默認的行為。
我認為部分的問題在于也許人們一直在使用future、promise或其他更簡單的、使用方式相似的reactive庫,所以他們自然地認為Rx在其他的情況下也是同樣的表現(xiàn),而這對于新手來說顯然是令人困惑的。
奇怪的是那些單方面的屬性對剛開始使用Rx的人們造成了很大的問題,有時候門檻會變得太高以至于人們變的沒有動力,但是從另一角度來看,這正是我個人認為Rx美的原因。
它美的地方在于你可以只通過一句話就能教會一個人使用Rx:Observable<T>代表了元素類型為T的可觀察序列(與Swift中的SequenceSequence是一樣的),其中每個元素都可以調(diào)用subscribe(和SequenceSequence中的generate方法相同)方法來注冊自己的接受序列元素的observer(返回信號)。
如果這方面很清楚的話,所有的其他東西都只是細節(jié),或變得非常明顯和自然。
將Rx看作另一個庫而不是一個不同的概念/抽象,并且開始在Stack
Overflow或相似的網(wǎng)站上開始查閱它,將會帶來許多問題。在學習Rx時,唯一需要學習的部分就是理解可觀察序列的句子,這樣的話其他的東西就很顯而易見了。這時候,陡峭的學習曲線就消失了。
RxSwift官方demo解析
下面是官網(wǎng)demo的一個小模塊,實現(xiàn)了Github的注冊功能,我們先看一下目錄結構(沒有標綠的文件是RxSwift專門為UI設計的更為精簡的實現(xiàn)方式,這里就不多做探討):

顯然這是一個標準的MVVM架構,在我看來RxSwift是MVVM的完美CP,RxSwift非常適合用來解決MVVM中,ViewModel的State和View綁定的問題。再扯一些題外話,demo中使用到了面向協(xié)議編程的思想,也是很值得大家參考的,能夠大幅度提高代碼的復用率和解耦合。推薦大家在慕課網(wǎng)上搜索2016swift開發(fā)者大會上,李潔信的演講,在這里我就不附上地址了。
接著看療效:

在正常情況下,完成2個密碼的比對功能我們需要在textField的代理方法中監(jiān)聽text屬性的變化,然后使用類似于這種結構的判斷:
if validPassWord(textfield.text) {
tipLabel1.text = ...
tipLabel.textColor = ...
} else {
code
}
if textfield1.text == textfield2.text {
tipLabel2.text = ...
tipLabel2.textColor = ...
}else {
code
}
事實上,實際情況遠比我的偽代碼復雜的多得多,大家應該都深有體會。那么RxSwift又是怎么完成這些功能的呢?我們先看一下viewController中的代碼(為了方便大家理解我會添加注釋):
override func viewDidLoad() {
super.viewDidLoad()
// 這里是viewModel的構造函數(shù),構造函數(shù)會接收三個TextField中的text生成的“序列”
// 只要textField.text發(fā)生改變,“序列”中就會有一個新的元素Element生成
let viewModel = GithubSignupViewModel1(
input: (
// .rx.text.orEmpty.asObservable()是RxSwift提供的UIKit的擴展,可以快速生成一個可觀察序列,以下同理
username: usernameOutlet.rx.text.orEmpty.asObservable(),
password: passwordOutlet.rx.text.orEmpty.asObservable(),
repeatedPassword: repeatedPasswordOutlet.rx.text.orEmpty.asObservable(),
loginTaps: signupOutlet.rx.tap.asObservable()
),
dependency: (
// 網(wǎng)絡接口
API: GitHubDefaultAPI.sharedAPI,
// 驗證輸入內(nèi)容是否合法的工具類
validationService: GitHubDefaultValidationService.sharedValidationService,
// 彈窗,點擊登錄按鈕后提示信息
wireframe: DefaultWireframe.shared
)
)
// viewModel構造函數(shù)中會處理出入的三個“序列”,然后生成以下五個可觀察“序列”
// let validatedUsername: Observable<ValidationResult> // 用戶名是否可用
// let validatedPassword: Observable<ValidationResult> // 密碼是否可用
// let validatedPasswordRepeated: Observable<ValidationResult> // 重復密碼是否可用
// let signupEnabled: Observable<Bool> // 注冊按鈕是否可以點擊
// let signedIn: Observable<Bool> // 是否玩成了注冊(控制彈窗)
// let signingIn: Observable<Bool> // 是否在注冊進程中(控制菊花的旋轉)
// ValidationResult是一個枚舉
enum ValidationResult {
case ok(message: String)
case empty
case validating
case failed(message: String)
}
// 有了以上這些序列,就可以在控制器中完成數(shù)據(jù)和控件的綁定
viewModel.signupEnabled
// 訂閱一個可觀察序列的事件處理閉包,只要有新的元素添加到序列中,該方法就會自動發(fā)出信號并執(zhí)行閉包中的代碼,并返回一個遵守Disposable的類型,Disposable就相當于垃圾回收,會自動銷毀序列。
.subscribe(onNext: { [weak self] valid in
self?.signupOutlet.isEnabled = valid
self?.signupOutlet.alpha = valid ? 1.0 : 0.5
})
.disposed(by: disposeBag)
viewModel.validatedUsername
/**
bind其實跟subscribe是等價的,只是他能更好的反應可觀察序列和觀察者之間的關系
站在一個代碼閱讀者的角度,用subscribe方法訂閱,你可能需要在事件處理序列中去尋找誰是觀察者,但是使用bind,你
可以很直觀的看到觀察者是誰,其實就是相當于對subscribe再次封裝一層,讓觀察者在自己的代碼區(qū)域完成事
件,就不用再寫在控制器中,代碼閱讀起來真叫一個流暢,有一種一目了然的感覺。也讓你不知不覺中就降低了
代碼的耦合度。
*/
.bind(to: usernameValidationOutlet.rx.validationResult)
.disposed(by: disposeBag)
viewModel.validatedPassword
.bind(to: passwordValidationOutlet.rx.validationResult)
.disposed(by: disposeBag)
viewModel.validatedPasswordRepeated
.bind(to: repeatedPasswordValidationOutlet.rx.validationResult)
.disposed(by: disposeBag)
viewModel.signingIn
.bind(to: signingUpOulet.rx.isAnimating)
.disposed(by: disposeBag)
viewModel.signedIn
.subscribe(onNext: { signedIn in
print("User signed in \(signedIn)")
})
.disposed(by: disposeBag)
//}
let tapBackground = UITapGestureRecognizer()
tapBackground.rx.event
.subscribe(onNext: { [weak self] _ in
self?.view.endEditing(true)
})
.disposed(by: disposeBag)
view.addGestureRecognizer(tapBackground)
}
總結
本來很想詳細的解構一下官方demo中的代碼解構,真的是十分優(yōu)秀簡潔的代碼,然后再說一說每個操作符的含義,但是這不是我寫這篇文章的初衷,我只想讓還沒有開始接觸到RxSwift的小伙伴稍稍感受一下RxSwift的魅力所在。如果你因為本文稍稍對RxSwift提起興趣,并打算學學看,那么下面這些博文千萬不要錯過:
RxSwift函數(shù)響應式編程
RxSwift給Swift帶來了原生Reactive編程的功能
是時候學習RxSwift了
[翻譯]RxSwift入門
希望讀者能指出本文中出現(xiàn)的錯誤,不勝感激。