RxSwift--讓你的開發(fā)變得簡(jiǎn)單高效

作為一個(gè)RxSwift初學(xué)者,寫這篇文章是很惶恐的(其中借鑒了很多大神的博文,后面會(huì)給出鏈接),但是管中窺豹,我會(huì)詳解RxSwift官方Demo中的一段代碼,讓還沒有接觸RxSwift的小伙伴感受到RxSwift為什么會(huì)讓我們的開發(fā)變得簡(jiǎn)單高效。

RxSwift到底是什么?

RxSwift是一種函數(shù)式響應(yīng)式編程。那什么是函數(shù)式編程呢,函數(shù)式編程最重要的概念就是“無(wú)狀態(tài)(immutable)”,看到這有些小伙伴可能會(huì)很開心,無(wú)狀態(tài)(知名LOL職業(yè)選手)嘛,我是他的粉絲!言歸正傳,到底什么是“無(wú)狀態(tài)(immutable)”呢?我看了很多文章,但是都被他們專業(yè)的描述整的一頭霧水,我來(lái)說(shuō)說(shuō)我的看法:有豐富debug經(jīng)驗(yàn)的小伙伴們都知道一個(gè)事實(shí),程序中80%的BUG都可以由追蹤變量的值來(lái)發(fā)現(xiàn),為什么呢?反過(guò)來(lái)想,引起B(yǎng)UG的最大元兇正是值的改變。說(shuō)道這里大家肯定都有了一個(gè)大膽的想法,倘若有一種方式能夠消除狀態(tài)的改變(這里為什么不說(shuō)“值”,因?yàn)镽x不僅僅是值的“無(wú)狀態(tài)(immutable)”,也會(huì)有事件的“無(wú)狀態(tài)(immutable)”,所以準(zhǔn)確的描述應(yīng)該是“狀態(tài)”),那么豈不是就沒什么BUG了?函數(shù)式編程正是為此而生。

那函數(shù)式編程究竟是怎么做到“無(wú)狀態(tài)(immutable)”這一點(diǎn)的呢?答案就是“序列(sequence)”,既然我不能改變狀態(tài),那我需要發(fā)生改變的時(shí)候,我就重新生成一個(gè)狀態(tài),發(fā)生幾次改變,我就生成幾個(gè)狀態(tài),而“序列(sequence)”就是盛放這些狀態(tài)的容器(大家可以看做是一個(gè)數(shù)組),那么狀態(tài)的改變是不是就不復(fù)存在?答案是肯定的,我只需要分別對(duì)“序列(sequence)”中的狀態(tài)做出命令,他們之間就不會(huì)互相影響,那么出bug的幾率自然大大降低。

那么既然成為了一個(gè)“序列”,怎么生成“序列”,怎么組合多個(gè)“序列”,怎么處理每個(gè)“序列”中的每個(gè)元素?RxSwift中有非常多的操作符來(lái)解決這些問(wèn)題,這也是RxSwift難以學(xué)習(xí)的原因之一。但是我們只要理解,這些操作符的的最終目標(biāo)是讓“序列”變成我們想要的樣子。記憶起來(lái)就會(huì)事半功倍。

說(shuō)完函數(shù)式,再來(lái)說(shuō)說(shuō)響應(yīng)式編程,先看段代碼:

 var a: Int = 0
 var b: Int = 0
 var c = a + b
 a = 1
 print(c)

盡管a在c打印之前賦值為1,但是在給c賦值的時(shí)候 a + b 還是等于 0 ,我們要想讓c得到最新的結(jié)果,就需要在c賦值之前將a的值改為1,那么如果a和b的值在之后的代碼中會(huì)一直變動(dòng),就需要不斷的給c寫一行重新賦值的代碼:c = a+ b,這真是太麻煩了,響應(yīng)式就是為了解決這個(gè)麻煩誕生的,當(dāng)a或者b發(fā)生變化的時(shí)候,c的值會(huì)響應(yīng)a或者b的值發(fā)生變化,所以就被稱之為響應(yīng)式了,那么響應(yīng)式如果實(shí)現(xiàn)呢,最簡(jiǎn)單容易理解的方式就是通過(guò)a或者b的屬性監(jiān)聽器,當(dāng)監(jiān)聽到a或者b發(fā)生變化時(shí),就重新給c賦值。實(shí)際上,無(wú)論代碼怎么寫,無(wú)非是在某一層隱式調(diào)用了c = a + b這一行代碼而已,即使我們直接去操作指針,也需要有一行代碼將兩個(gè)指針指向的值相加,所以響應(yīng)式并不是難以理解的技術(shù),但是響應(yīng)式編程能夠極大的改善代碼的可讀性,更直觀,更容易維護(hù)。

RxSwift,就是將函數(shù)式,響應(yīng)式,以及鏈?zhǔn)骄幊痰慕Y(jié)合體,即使在項(xiàng)目中由于各種原因不能去使用,讀懂它,也能讓你在寫業(yè)務(wù)代碼的時(shí)候更加游刃有余。

在這里再多說(shuō)幾句:有很多博客把“序列(sequence)”比作“流、信號(hào)、管道”等,這確實(shí)有助于大家理解RxSwift,但是RxSwift的項(xiàng)目維護(hù)者顯然不這么認(rèn)為,下面給大家看一段InfoQ對(duì)項(xiàng)目維護(hù)者Krunoslav Zaher的采訪:

使用不一樣的、不太準(zhǔn)確的方式思考可觀察的序列會(huì)帶來(lái)很多問(wèn)題(流、信號(hào)、導(dǎo)管等)。那些東西有時(shí)候可以用來(lái)幫助新手探索Rx,但是如果使用的時(shí)間過(guò)長(zhǎng),它們只會(huì)在接下來(lái)的開發(fā)中帶來(lái)很多困惑。人們有時(shí)候使用它們是因?yàn)镽x可以用在模擬系統(tǒng)的多狀態(tài)部分,但是那只是故事的一部分,而我建議大家不要那樣思考。

使用那些項(xiàng)(流、信號(hào)、導(dǎo)管等)思考的問(wèn)題在于它們帶有一個(gè)隱含假定:
定義Observable時(shí)帶有共享的值,并且可以從外部設(shè)定的當(dāng)前值也是一樣的。而Observable會(huì)被莫名其妙地直接取消,這看起來(lái)像future或promise。

而事實(shí)上:
Observable只是一個(gè)如何計(jì)算序列的定義。沒有計(jì)算會(huì)在序列定義后(let result = source.map(transformObservableSequence))自動(dòng)地執(zhí)行。這和Swift中的SequenceSequence是一樣的。當(dāng)綁定一個(gè)觀察者時(shí)(這與調(diào)用SequenceType中的generate方法相同)才會(huì)執(zhí)行計(jì)算,并且可以通過(guò)處理Disposable對(duì)象的結(jié)果來(lái)取消特定的計(jì)算。Observable當(dāng)然可以代表多狀態(tài)的計(jì)算,它們共享潛在的綁定并source可觀察的序列(使用shareReplay(1)操作等),但是這不是默認(rèn)的行為。

我認(rèn)為部分的問(wèn)題在于也許人們一直在使用future、promise或其他更簡(jiǎn)單的、使用方式相似的reactive庫(kù),所以他們自然地認(rèn)為Rx在其他的情況下也是同樣的表現(xiàn),而這對(duì)于新手來(lái)說(shuō)顯然是令人困惑的。
奇怪的是那些單方面的屬性對(duì)剛開始使用Rx的人們?cè)斐闪撕艽蟮膯?wèn)題,有時(shí)候門檻會(huì)變得太高以至于人們變的沒有動(dòng)力,但是從另一角度來(lái)看,這正是我個(gè)人認(rèn)為Rx美的原因。

它美的地方在于你可以只通過(guò)一句話就能教會(huì)一個(gè)人使用Rx:Observable<T>代表了元素類型為T的可觀察序列(與Swift中的SequenceSequence是一樣的),其中每個(gè)元素都可以調(diào)用subscribe(和SequenceSequence中的generate方法相同)方法來(lái)注冊(cè)自己的接受序列元素的observer(返回信號(hào))。
如果這方面很清楚的話,所有的其他東西都只是細(xì)節(jié),或變得非常明顯和自然。
將Rx看作另一個(gè)庫(kù)而不是一個(gè)不同的概念/抽象,并且開始在Stack
Overflow或相似的網(wǎng)站上開始查閱它,將會(huì)帶來(lái)許多問(wèn)題。在學(xué)習(xí)Rx時(shí),唯一需要學(xué)習(xí)的部分就是理解可觀察序列的句子,這樣的話其他的東西就很顯而易見了。這時(shí)候,陡峭的學(xué)習(xí)曲線就消失了。

RxSwift官方demo解析

下面是官網(wǎng)demo的一個(gè)小模塊,實(shí)現(xiàn)了Github的注冊(cè)功能,我們先看一下目錄結(jié)構(gòu)(沒有標(biāo)綠的文件是RxSwift專門為UI設(shè)計(jì)的更為精簡(jiǎn)的實(shí)現(xiàn)方式,這里就不多做探討):

目錄結(jié)構(gòu).png

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

接著看療效:


演示.gif

在正常情況下,完成2個(gè)密碼的比對(duì)功能我們需要在textField的代理方法中監(jiān)聽text屬性的變化,然后使用類似于這種結(jié)構(gòu)的判斷:

if  validPassWord(textfield.text) {
      tipLabel1.text = ...
      tipLabel.textColor = ...
} else {
      code
}

if textfield1.text == textfield2.text {
      tipLabel2.text = ...
      tipLabel2.textColor = ...
}else {
      code
}

事實(shí)上,實(shí)際情況遠(yuǎn)比我的偽代碼復(fù)雜的多得多,大家應(yīng)該都深有體會(huì)。那么RxSwift又是怎么完成這些功能的呢?我們先看一下viewController中的代碼(為了方便大家理解我會(huì)添加注釋):

override func viewDidLoad() {
        super.viewDidLoad()
        //  這里是viewModel的構(gòu)造函數(shù),構(gòu)造函數(shù)會(huì)接收三個(gè)TextField中的text生成的“序列”
        //  只要textField.text發(fā)生改變,“序列”中就會(huì)有一個(gè)新的元素Element生成
        let viewModel = GithubSignupViewModel1(
            input: (
                // .rx.text.orEmpty.asObservable()是RxSwift提供的UIKit的擴(kuò)展,可以快速生成一個(gè)可觀察序列,以下同理   
                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)絡(luò)接口
                API: GitHubDefaultAPI.sharedAPI,
                //  驗(yàn)證輸入內(nèi)容是否合法的工具類
                validationService: GitHubDefaultValidationService.sharedValidationService,
                //  彈窗,點(diǎn)擊登錄按鈕后提示信息
                wireframe: DefaultWireframe.shared
            )
        )

        //  viewModel構(gòu)造函數(shù)中會(huì)處理出入的三個(gè)“序列”,然后生成以下五個(gè)可觀察“序列”
        //  let validatedUsername: Observable<ValidationResult>          //  用戶名是否可用
        //  let validatedPassword: Observable<ValidationResult>          //  密碼是否可用
        //  let validatedPasswordRepeated: Observable<ValidationResult>  //  重復(fù)密碼是否可用
        //  let signupEnabled: Observable<Bool>                          //  注冊(cè)按鈕是否可以點(diǎn)擊
        //  let signedIn: Observable<Bool>                               //  是否玩成了注冊(cè)(控制彈窗)
        //  let signingIn: Observable<Bool>                              //  是否在注冊(cè)進(jìn)程中(控制菊花的旋轉(zhuǎn))
       //   ValidationResult是一個(gè)枚舉
       enum ValidationResult {
            case ok(message: String)
            case empty
            case validating
            case failed(message: String)
       }
        // 有了以上這些序列,就可以在控制器中完成數(shù)據(jù)和控件的綁定
        viewModel.signupEnabled
            //  訂閱一個(gè)可觀察序列的事件處理閉包,只要有新的元素添加到序列中,該方法就會(huì)自動(dòng)發(fā)出信號(hào)并執(zhí)行閉包中的代碼,并返回一個(gè)遵守Disposable的類型,Disposable就相當(dāng)于垃圾回收,會(huì)自動(dòng)銷毀序列。
            .subscribe(onNext: { [weak self] valid  in
                self?.signupOutlet.isEnabled = valid
                self?.signupOutlet.alpha = valid ? 1.0 : 0.5
            })
            .disposed(by: disposeBag)

        viewModel.validatedUsername
            /**
            bind其實(shí)跟subscribe是等價(jià)的,只是他能更好的反應(yīng)可觀察序列和觀察者之間的關(guān)系
            站在一個(gè)代碼閱讀者的角度,用subscribe方法訂閱,你可能需要在事件處理序列中去尋找誰(shuí)是觀察者,但是使用bind,你
            可以很直觀的看到觀察者是誰(shuí),其實(shí)就是相當(dāng)于對(duì)subscribe再次封裝一層,讓觀察者在自己的代碼區(qū)域完成事
            件,就不用再寫在控制器中,代碼閱讀起來(lái)真叫一個(gè)流暢,有一種一目了然的感覺。也讓你不知不覺中就降低了
            代碼的耦合度。
            */
            .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)
    }

總結(jié)

本來(lái)很想詳細(xì)的解構(gòu)一下官方demo中的代碼解構(gòu),真的是十分優(yōu)秀簡(jiǎn)潔的代碼,然后再說(shuō)一說(shuō)每個(gè)操作符的含義,但是這不是我寫這篇文章的初衷,我只想讓還沒有開始接觸到RxSwift的小伙伴稍稍感受一下RxSwift的魅力所在。如果你因?yàn)楸疚纳陨詫?duì)RxSwift提起興趣,并打算學(xué)學(xué)看,那么下面這些博文千萬(wàn)不要錯(cuò)過(guò):
RxSwift函數(shù)響應(yīng)式編程
RxSwift給Swift帶來(lái)了原生Reactive編程的功能
是時(shí)候?qū)W習(xí)RxSwift了
[翻譯]RxSwift入門

希望讀者能指出本文中出現(xiàn)的錯(cuò)誤,不勝感激。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容