GitHub上最流行的函數(shù)響應(yīng)式編程庫(kù):ReactiveCocoa與RxSwift框架大比較
譯文一、簡(jiǎn)介
如今,函數(shù)響應(yīng)式編程成為越來(lái)越受Swift開發(fā)商歡迎的編程方法。原因很簡(jiǎn)單,它能使復(fù)雜的異步代碼容易地編寫和理解。
在這篇文章中,我要比較GitHub上提供的兩個(gè)***的函數(shù)響應(yīng)式編程庫(kù)——RxSwift與ReactiveCocoa。
首先,我們將簡(jiǎn)要回顧什么是函數(shù)響應(yīng)式編程,然后詳細(xì)比較這兩個(gè)框架。本文結(jié)束時(shí),你會(huì)決定哪一個(gè)框架更適合你!
二、什么是函數(shù)響應(yīng)式編程(FRP)
甚至在蘋果公司宣布Swift語(yǔ)言之前,函數(shù)響應(yīng)式編程在近幾年已人氣劇增,從而與面向?qū)ο缶幊绦纬甚r明對(duì)比。從Haskell到Javascript,你都能夠發(fā)現(xiàn)其中包含著受函數(shù)響應(yīng)式編程啟發(fā)而存在的支持。這是為什么?函數(shù)響應(yīng)式編程提供了什么特別的支持?也許最重要的是,你該怎樣在Swift編程中使用這一技術(shù)?
FRP是一種由Conal Elliott創(chuàng)建的編程范式。他定義了有關(guān)FRP的非常具體的語(yǔ)義(請(qǐng)參考https://stackoverflow.com/questions/1028250/what-is-functional-reactive-programming)。為了實(shí)現(xiàn)更簡(jiǎn)潔的定義,F(xiàn)RP組合了另外兩個(gè)功能概念:
l 響應(yīng)式編程(Reactive Programming):側(cè)重于異步數(shù)據(jù)流,你可以監(jiān)聽這種數(shù)據(jù)流并迅速做出相應(yīng)的反應(yīng)。要了解與之相關(guān)的更多信息,請(qǐng)查閱https://gist.github.com/staltz/868e7e9bc2a7b8c1f754。
l 函數(shù)式編程(Functional Programming):強(qiáng)調(diào)通過(guò)數(shù)學(xué)語(yǔ)言風(fēng)格的函數(shù)、不變性和表現(xiàn)力來(lái)實(shí)現(xiàn)計(jì)算,并最小化變量和狀態(tài)的使用。請(qǐng)參閱百度百科(http://baike.baidu.com/link?url=oTiEJsaX5ibGvK3R-BK6J55dGOXf3-Zzn5e1uBpvWDx4AjCV8ykQtRWz3RmuUefjD6IzL_QZcyedkvMB8sEyhK)進(jìn)一步了解這種編程范式。
(一)一個(gè)簡(jiǎn)單的例子
當(dāng)然,理解上述概念的最容易的方式是通過(guò)一個(gè)簡(jiǎn)單的實(shí)例加以說(shuō)明?,F(xiàn)在,我們來(lái)構(gòu)建一個(gè)小程序,它將實(shí)現(xiàn)用戶位置的定位功能,并當(dāng)該用戶靠近一家咖啡館時(shí)及時(shí)地通知他。
如果你想使用函數(shù)響應(yīng)式編程來(lái)開發(fā)上述程序,你需要:
1. 創(chuàng)建一個(gè)能夠發(fā)出你能夠進(jìn)行響應(yīng)的位置事件的信息流。
2. 然后,你要對(duì)位置事件進(jìn)行過(guò)濾,以便確定哪些位置事件對(duì)應(yīng)于靠近咖啡館這種信息,然后發(fā)送相應(yīng)的警告。
使用ReactiveCocoa編程實(shí)現(xiàn)上述功能的代碼大致如下所示:
- locationProducer // 1
- .filter(ifLocationNearCoffeeShops) // 2
- .startWithNext {[weak self] location in // 3
- self?.alertUser(location)
- }
下面作簡(jiǎn)要分析。
1. locationProducer負(fù)責(zé)每當(dāng)位置改變時(shí)發(fā)出一個(gè)事件。注意:這在ReactiveCocoa編程中稱作“信號(hào)”(signal),而在RxSwift中稱作“順序”(sequence)。
2. 然后,使用函數(shù)編程技術(shù)來(lái)響應(yīng)位置更新。filter方法執(zhí)行與數(shù)組上過(guò)濾操作完全相同的功能,負(fù)責(zé)把每個(gè)值傳遞給函數(shù)ifLocationNearCoffeeShops。如果該函數(shù)返回true,則該事件被允許進(jìn)行下一步處理。
3. ***,startWithNext形成對(duì)過(guò)濾后信號(hào)的訂閱。于是每當(dāng)事件到達(dá),閉包中的表達(dá)式都被執(zhí)行。
上面的代碼看上去與轉(zhuǎn)換值數(shù)組的代碼極其相似。但是這里有一點(diǎn)特別“聰明”——這段代碼是異步執(zhí)行的;隨著位置事件的發(fā)生filter函數(shù)和閉包表達(dá)式被相應(yīng)地調(diào)用。
當(dāng)然,你可能會(huì)對(duì)其所用語(yǔ)法感到有點(diǎn)奇怪,但希望這段代碼的基本意圖你應(yīng)該清楚。這就是函數(shù)式編程的美麗所在:它是聲明性語(yǔ)言。它向你展示發(fā)生了什么,而不是如何能做的細(xì)節(jié)實(shí)現(xiàn)。
(二)事件轉(zhuǎn)換
在上面的定位示例中,你才剛剛學(xué)會(huì)如何觀察(observe)流,除了過(guò)濾出咖啡館附近位置信息外,你其實(shí)并沒(méi)有對(duì)這些事件做更多的事情。
FRP的另一個(gè)基本點(diǎn)是把這些事件組合一起并把它們轉(zhuǎn)換為有意義的內(nèi)容。為此,你要使用(但不限于)高階函數(shù)。
不出所料,你會(huì)在Swift函數(shù)式編程中經(jīng)常遇到如下內(nèi)容:map、filter、reduce、combine和zip。
讓我們修改一下上面的定位示例,以便跳過(guò)重復(fù)的位置信息而把傳入的位置信息(對(duì)應(yīng)于CLLocation結(jié)構(gòu))轉(zhuǎn)換成更富人性化的消息。
- locationProducer
- .skipRepeats() // 1
- .filter(ifLocationNearCoffeeShops)
- .map(toHumanReadableLocation) // 2
- .startWithNext {[weak self] readableLocation in
- self?.alertUser(readableLocation)
- }
下面讓我們來(lái)分析一下上面新添加的兩行代碼:
1. 首先對(duì)通過(guò)locationProducer發(fā)出的信號(hào)應(yīng)用skipRepeats操作。注意,這種操作并沒(méi)有模擬數(shù)組的意思,而是ReactiveCocoa特有的。其執(zhí)行的函數(shù)功能是很容易理解的:過(guò)濾掉重復(fù)的事件。
2. 在執(zhí)行過(guò)濾函數(shù)后,調(diào)用map來(lái)把事件數(shù)據(jù)從一種類型轉(zhuǎn)換成另一種類型,有可能從CLLocation轉(zhuǎn)換成String。
至此,你應(yīng)該了解了FRP編程的優(yōu)點(diǎn):
l 簡(jiǎn)單有力;
l 基于聲明式表達(dá)方式便代碼更易于理解;
l 復(fù)雜的流程變得更易于管理和描述。
三、ReactiveCocoa與RxSwift框架簡(jiǎn)介
現(xiàn)在,你已經(jīng)更好地了解了FRP是什么以及它如何可以幫助你使復(fù)雜的異步流更容易管理。接下來(lái),讓我們考察兩個(gè)當(dāng)前***的FRP框架——ReactiveCocoa和RxSwift,并進(jìn)一步了解為什么你可能選擇其中之一。
在正式討論有關(guān)細(xì)節(jié)之前,讓我們簡(jiǎn)要介紹一下每個(gè)框架的歷史。
(一)ReactiveCocoa框架
ReactiveCocoa框架(https://github.com/ReactiveCocoa/ReactiveCocoa)發(fā)布在GitHub網(wǎng)站上。在GitHub Mac客戶端上工作時(shí),開發(fā)人員發(fā)現(xiàn)自己疲于應(yīng)對(duì)其應(yīng)用程序的數(shù)據(jù)流。他們從微軟的ReactiveExtensions(一個(gè)針對(duì)C#的FRP框架)框架中找到靈感,然后開發(fā)出他們自己的Objective-C實(shí)現(xiàn)版本。
當(dāng)開發(fā)團(tuán)隊(duì)正在他們Objective-C3.0版本上進(jìn)行研發(fā)時(shí)Swift正式宣布發(fā)行。他們意識(shí)到,Swift的函數(shù)天性正好彌補(bǔ)了ReactiveCocoa,于是他們馬上著手實(shí)現(xiàn)Swift上的3.0版本。該3.0版本充分地利用了柯里化(curring)和pipe-forward運(yùn)算符技術(shù)。
Swift 2.0引入了面向協(xié)議編程思想,從而導(dǎo)致ReactiveCocoa API發(fā)展歷程中的另一個(gè)重大變化,隨著版本4.0發(fā)布的臨近,pipe-forward運(yùn)算符支持協(xié)議擴(kuò)展。
在本文寫作之時(shí),ReactiveCocoa已經(jīng)成為一個(gè)GitHub網(wǎng)站上點(diǎn)贊超過(guò)13,000星的大熱庫(kù)。
(二)RxSwift框架
微軟的ReactiveExtensions啟發(fā)了大量的框架把FRP概念加入到JavaScript、Java、Scala和其他眾多語(yǔ)言中。這最終導(dǎo)致ReactiveX的形成。ReactiveX其實(shí)是一個(gè)開發(fā)小組,它能夠創(chuàng)建一批針對(duì)FRP實(shí)現(xiàn)的通用API;這將允許不同的框架開發(fā)人員協(xié)同工作。其結(jié)局是,熟悉Scala的RxScala開發(fā)人員能夠相對(duì)容易過(guò)渡到Java的等效實(shí)現(xiàn)——RxJava。
RxSwift是最近才加入到ReactiveX中的,因此目前還沒(méi)有像ReactiveCocoa(在本文寫作之時(shí),在GitHub已經(jīng)獲得大約4,000個(gè)星的點(diǎn)贊)那樣普及。然而,RxSwift是ReactiveX的一部分的事實(shí)無(wú)疑將有助于它的流行和長(zhǎng)久化。
有趣的是,RxSwift和ReactiveCocoa分享著一個(gè)共同的祖代實(shí)現(xiàn)——ReactiveExtensions!
四、ReactiveCocoa與RxSwift框架比較
承接前面所提到的,現(xiàn)在我們來(lái)關(guān)注細(xì)節(jié)內(nèi)容。ReactiveCocoa與RxSwift框架在FRP支持方面擁有許多的不同之處。在此僅討論幾個(gè)關(guān)鍵部分。
(一)熱信號(hào)與冷信號(hào)
想象一下,你需要發(fā)出網(wǎng)絡(luò)請(qǐng)求、解析響應(yīng)并向用戶顯示有關(guān)信息,例如類似于下面的代碼:
- let requestFlow = networkRequest.flatMap(parseResponse)
- requestFlow.startWithNext {[weak self] result in
- self?.showResult(result)
- }
當(dāng)訂閱信號(hào)(使用startWithNext)時(shí),將啟動(dòng)網(wǎng)絡(luò)請(qǐng)求。這些信號(hào)被稱為“冷”信號(hào),因?yàn)樗鼈兲幱?ldquo;凍結(jié)”狀態(tài)——直到你實(shí)際上訂閱這些信號(hào)。
另一方面是“熱”信號(hào)。當(dāng)訂閱其中之一時(shí),它可能已經(jīng)啟動(dòng);因此,你正在觀察的可能是第三或第四個(gè)相應(yīng)的事件。一種典型的例子是敲打鍵盤。所謂“開始”敲打其實(shí)并無(wú)多大意義,對(duì)于服務(wù)器請(qǐng)求也是如此。
讓我們回顧一下:
l 冷信號(hào)是:當(dāng)你訂閱它時(shí)你才啟動(dòng)。每個(gè)新的訂閱服務(wù)器啟動(dòng)這項(xiàng)工作。訂閱requestFlow三次意味著發(fā)出三次網(wǎng)絡(luò)請(qǐng)求。
l 熱信號(hào)是:已經(jīng)可以發(fā)送事件。新的訂閱服務(wù)器不去啟動(dòng)它。通常,UI交互就是熱信號(hào)。
ReactiveCocoa針對(duì)熱信號(hào)和冷信號(hào)提供了對(duì)應(yīng)的類型支持:Signal<T, E>和SignalProducer<T, E>。然而,RxSwift使用了一種適用于上述兩種類型的結(jié)構(gòu)Observable<T>。
提供不同的類型來(lái)表示熱和冷信號(hào)有必要嗎?
就個(gè)人而言,我發(fā)現(xiàn)了解信號(hào)的語(yǔ)義是很重要的,因?yàn)樗玫孛枋隽巳绾卧谔囟ǖ恼Z(yǔ)境中使用它。當(dāng)處理復(fù)雜的系統(tǒng)時(shí),這可能有很大的區(qū)別。
且不說(shuō)是否提供不同類型,僅了解熱和冷信號(hào)而言就非常重要。
假設(shè)你正在處理一個(gè)熱信號(hào),但經(jīng)證明它原來(lái)是一個(gè)冷信號(hào),針對(duì)每個(gè)新的訂閱服務(wù)器你將會(huì)以副作用方式啟動(dòng),這對(duì)你的應(yīng)用程序可能產(chǎn)生巨大的影響。一個(gè)常見的例子就是,在你的應(yīng)用程序中存在三個(gè)或四個(gè)地方想觀察網(wǎng)絡(luò)請(qǐng)求,而針對(duì)每一個(gè)新的訂閱將開始一個(gè)不同的請(qǐng)求。
(二) 錯(cuò)誤處理
讓我們談到錯(cuò)誤處理之前,不妨扼要地重述一下在RxSwift和ReactiveCocoa中調(diào)度的事件性質(zhì)。在這兩個(gè)框架中,都提供了三個(gè)主要事件:
1. Next<T>:每當(dāng)有新值(類型T)推入到事件流時(shí),將發(fā)送此事件。在上面的定位器示例中,T是CLLocation。
2. Completed:指示事件流已經(jīng)結(jié)束。此事件發(fā)生后,不再發(fā)送Next <T>或Error <E>。
3. Error:指示一個(gè)錯(cuò)誤。在上面的服務(wù)器請(qǐng)求示例中,如果你有一個(gè)服務(wù)器錯(cuò)誤,會(huì)發(fā)送此事件。E描述了符合ErrorType協(xié)議的數(shù)據(jù)類型。此事件發(fā)生后,不會(huì)再發(fā)送Next或者Completed。
你可能已經(jīng)注意到在前面討論熱和冷信號(hào)內(nèi)容時(shí)ReactiveCocoa中的Signal<T,E>和SignalProducer<T,E>都使用了兩個(gè)參數(shù)化的類型,而RxSwift的Observable <T>僅提供了一個(gè)。其中,第二種類型(E)是指符合ErrorType協(xié)議的類型。在RxSwift中,該類型被忽略,而在內(nèi)部被視為一種符合ErrorType協(xié)議的類型。
那么,這一切意味著什么呢?
用通俗易懂的話說(shuō),它意味著在RxSwift 中可以通過(guò)多種不同的方式發(fā)出錯(cuò)誤信息:
- create { observer in
- observer.onError(NSError.init(domain: "NetworkServer", code: 1, userInfo: nil))
- }
上述代碼創(chuàng)建了一個(gè)信號(hào)(或使用RxSwift術(shù)語(yǔ)來(lái)說(shuō),是一個(gè)可觀察的序列)并立即發(fā)出一個(gè)錯(cuò)誤。
下面給出一種可替代的表達(dá)方式:
- create { observer in
- observer.onError(MyDomainSpecificError.NetworkServer)
- }
因?yàn)橐粋€(gè)Observable只強(qiáng)制要求錯(cuò)誤必須是符合ErrorType協(xié)議的類型,所以,你差不多可以發(fā)送任何你想要的。但也有一點(diǎn)尷尬的問(wèn)題,請(qǐng)參考如下代碼:
- enum MyDomanSpecificError: ErrorType {
- case NetworkServer
- case Parser
- case Persistence
- }
- func handleError(error: MyDomanSpecificError) {
- // Show alert with the error
- }
- observable.subscribeError {[weak self] error in
- self?.handleError(error)
- }
上述代碼是不會(huì)工作的,因?yàn)楹瘮?shù)handleError期待是MyDomainSpecificError類型而不是ErrorType。為此,你被迫要做兩件事:
1.嘗試把error轉(zhuǎn)換成MyDomanSpecificError。
2.當(dāng)不能把error轉(zhuǎn)換成MyDomanSpecificError時(shí),自己來(lái)處理這種情況。
***點(diǎn)可以輕易通過(guò)as?語(yǔ)法技術(shù)加以修復(fù),但第二種情況較難處理一些。一種潛在的解決方案是引入一種Unknown類型:
- enum MyDomanSpecificError: ErrorType {
- case NetworkServer
- case Parser
- case Persistence
- case Unknown
- }
- observable.subscribeError {[weak self] error in
- self?.handleError(error as? MyDomanSpecificError ?? .Unknown)
- }
在ReactiveCocoa中,當(dāng)創(chuàng)建一個(gè)Signal<T,E>或SignalProducer<T,E>時(shí)因?yàn)槟阕髁祟愋?ldquo;修復(fù)”,如果你嘗試發(fā)送別的東西時(shí)編譯器會(huì)發(fā)出抱怨。因此,底線是:在ReactiveCocoa中,編譯器僅允許發(fā)送與你期望相同的錯(cuò)誤。
這是ReactiveCocoa值得點(diǎn)贊的又一個(gè)方面!
(三) UI綁定
標(biāo)準(zhǔn)iOSAPI,如UIKit,并不使用FRP。為了使用RxSwift或ReactiveCocoa,你必須彌合這些Api,例如把設(shè)備的點(diǎn)按操作轉(zhuǎn)換成信號(hào)或可觀測(cè)對(duì)象。
正如你所想象的,這其中蘊(yùn)藏著“巨大能量”;為此,ReactiveCocoa和RxSwift都各自提供大量的橋接函數(shù)及開箱即用的綁定支持。
ReactiveCocoa從其早期的Objective C時(shí)代帶來(lái)了很多好東西。你會(huì)發(fā)現(xiàn)已經(jīng)做了大量的工作,這已經(jīng)彌合了與Swift共同使用的問(wèn)題。這其中包括UI綁定,以及其他目前尚未翻譯為Swift的運(yùn)算符。當(dāng)然,這稍微有點(diǎn)奇怪,你正在使用不屬于SwiftAPI部分(如RACSignal)的一些數(shù)據(jù)類型,這將迫使用戶把Objective C類型轉(zhuǎn)換為Swift類型。
不只如此,我覺(jué)得我們花費(fèi)了更多時(shí)間來(lái)討論源碼而不是文檔,這已經(jīng)慢慢地落后于時(shí)代了。不過(guò),要注意的是,文檔從理論角度看的確是優(yōu)秀的,只是沒(méi)有更多地關(guān)注實(shí)用方面。
為了彌補(bǔ)這種情況,你可以自行查閱一部分ReactiveCocoa教程。
另一方面,RxSwift綁定易于使用!不只是提供了一個(gè)龐大的分類目錄(https://github.com/ReactiveX/RxSwift/blob/master/Documentation/API.md),也提供了大量的范例(https://github.com/ReactiveX/RxSwift/blob/master/Documentation/Examples.md),以及更完整的文檔(https://github.com/ReactiveX/RxSwift/tree/master/Documentation)。對(duì)于一些人來(lái)說(shuō),這已經(jīng)是選擇RxSwift而勝過(guò)選擇ReactiveCocoa的足夠理由。
這是RxSwift值得點(diǎn)贊的又一個(gè)方面!
(四)社團(tuán)支持
ReactiveCocoa出現(xiàn)的歷史已經(jīng)遠(yuǎn)遠(yuǎn)超過(guò)RxSwift。有許多人可以繼續(xù)開展這項(xiàng)工作,而且有相當(dāng)數(shù)量的在線教程。此外,StackOverflow網(wǎng)站也提供了專門針對(duì)它(https://stackoverflow.com/questions/tagged/reactive-cocoa)的子論壇。
ReactiveCocoa有一個(gè)專門的Slack群,但很小,只有209人,所以經(jīng)常有很多問(wèn)題未得到及時(shí)解答。在緊急關(guān)頭,我有時(shí)不得不聯(lián)系ReactiveCocoa核心成員請(qǐng)教,當(dāng)然假設(shè)別人也有類似的需求。盡管如此,你最有可能找到一些在線教程來(lái)解釋你的問(wèn)題。
相對(duì)于ReactiveCocoa,RxSwift自然更新一些,目前基本呈現(xiàn)出“很多人看一個(gè)人表演”的狀態(tài)。它也有一個(gè)專門的Slack群,有961名成員,而且面臨著比這個(gè)數(shù)目大得多的討論量。如果有相關(guān)問(wèn)題,你總能在此群中找到人來(lái)幫助你。
(五)使用哪一個(gè)更好
讀者Ash Furrow的建議是:如果你是一位新手,那么選擇從哪一個(gè)框架開始都無(wú)所謂。不錯(cuò),的確存在許多技術(shù)方面的不同,但是對(duì)于新手來(lái)說(shuō)這些內(nèi)容都是很有意思的。你可以嘗試使用一個(gè)框架,再試試另一個(gè)框架。由你自己確定到底哪一個(gè)更符合你自己,然后你就能夠理解你為什么選擇這個(gè)框架了。
如果你是一位新手,我也建議你這樣做。其實(shí),只有你積累了足夠豐富的經(jīng)驗(yàn)時(shí)你才會(huì)欣賞到二者之間的奧妙區(qū)別。
但是,如果你擔(dān)任著一種特殊職務(wù),你需要選擇其一,并且沒(méi)有時(shí)間來(lái)隨意體驗(yàn),那么我的建議如下:
建議選擇ReactiveCocoa框架:
l 如果你想更好地描述你的系統(tǒng)。并且,想使用不同的類型來(lái)區(qū)別熱信號(hào)和冷信號(hào),并且在錯(cuò)誤處理方面使用類型化參數(shù),這些會(huì)為你的系統(tǒng)開發(fā)帶來(lái)驚喜。
l 如果你想使用大規(guī)模測(cè)試框架,為許多人使用,并應(yīng)用于許多項(xiàng)目中。
建議選擇RxSwift框架:
l 如果UI綁定對(duì)你的工程很重要。
l 如果你是一位FRP新手,并需要及時(shí)的幫助信息。
l 如果你已經(jīng)了解了RxJS或者RxJava。那么,既然這兩個(gè)框架和RxSwift框架都隸于Reactivex組織,一旦你熟悉了其中之一,剩下的也就是語(yǔ)法問(wèn)題了。
五、小結(jié)
無(wú)論選擇RxSwift還是ReactiveCocoa框架,你都不會(huì)后悔的。這兩個(gè)都是功能極其強(qiáng)大的框架,都會(huì)幫助你更好地描述你的系統(tǒng)。
值得注意的是,一旦你選擇了RxSwift還是ReactiveCocoa框架,在兩者之間來(lái)回切換只是幾個(gè)小時(shí)的問(wèn)題。就我的體驗(yàn)來(lái)說(shuō),從ReactiveCocoa轉(zhuǎn)到RxSwift最關(guān)鍵的是熟悉其錯(cuò)誤處理技術(shù)??傊?,最關(guān)鍵的問(wèn)題在于克服FRP編程技術(shù),而不是具體的實(shí)現(xiàn)方面。
***,提供幾個(gè)鏈接供你在學(xué)習(xí)上述兩個(gè)框架道路上作為參考:
l Conal Elliott的博客(http://conal.net/blog/);
l Conal Elliott在Stackoverflow網(wǎng)站上的重要問(wèn)答(https://stackoverflow.com/questions/1028250/what-is-functional-reactive-programming);
l André Staltz的重要文章“Why I cannot say FRP but I just did”(https://medium.com/@andrestaltz/why-i-cannot-say-frp-but-i-just-did-d5ffaa23973b#.62dnhk32p);
l RxSwift代碼倉(cāng)庫(kù)(https://github.com/ReactiveX/RxSwift);
l ReactiveCocoa代碼倉(cāng)庫(kù)(https://github.com/ReactiveCocoa/ReactiveCocoa);
l Rex代碼倉(cāng)庫(kù)(https://github.com/neilpa/Rex);
l 針對(duì)iOS開發(fā)者的FRP寶庫(kù)(https://gist.github.com/JaviLorbada/4a7bd6129275ebefd5a6)。
l RxSwift探討資源(http://rx-marin.com/)。
原文標(biāo)題:ReactiveCocoa vs RxSwift
【51CTO.com獨(dú)家譯文,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明來(lái)源】