什么是ReactiveCocoa
ReactiveCocoa(其簡稱為RAC)是由Github開源的一個(gè)應(yīng)用于iOS和OS X開發(fā)的新框架忍抽。RAC具有函數(shù)式編程和響應(yīng)式編程的特性雅倒。它主要吸取了.Net的Reactive Extensions的設(shè)計(jì)和實(shí)現(xiàn)。
ReactiveCocoa試圖解決什么問題
經(jīng)過一段時(shí)間的研究仑乌,我認(rèn)為ReactiveCocoa試圖解決以下3個(gè)問題:
傳統(tǒng)iOS開發(fā)過程中,狀態(tài)以及狀態(tài)之間依賴過多的問題
傳統(tǒng)MVC架構(gòu)的問題:Controller比較復(fù)雜,可測(cè)試性差
提供統(tǒng)一的消息傳遞機(jī)制
傳統(tǒng)iOS開發(fā)過程中,狀態(tài)以及狀態(tài)之間依賴過多的問題
我們?cè)陂_發(fā)iOS應(yīng)用時(shí)禁荒,一個(gè)界面元素的狀態(tài)很可能受多個(gè)其它界面元素或后臺(tái)狀態(tài)的影響。
例如角撞,在用戶帳戶的登錄界面呛伴,通常會(huì)有2個(gè)輸入框(分別輸入帳號(hào)和密碼)和一個(gè)登錄按鈕。如果我們要加入一個(gè)限制條件:當(dāng)用戶輸入完帳號(hào)和密碼谒所,并且登錄的網(wǎng)絡(luò)請(qǐng)求還未發(fā)出時(shí)热康,確定按鈕才可以點(diǎn)擊。通常情況下劣领,我們需要監(jiān)聽這兩個(gè)輸入框的狀態(tài)變化以及登錄的網(wǎng)絡(luò)請(qǐng)求狀態(tài)姐军,然后修改另一個(gè)控件的enabled狀態(tài)。
傳統(tǒng)的寫法如下(該示例代碼修改自ReactiveCocoa官網(wǎng)) :
12345678910111213141516171819202122232425262728293031
staticvoid*ObservationContext=&ObservationContext;-(void)viewDidLoad{[superviewDidLoad];[LoginManager.sharedManageraddObserver:selfforKeyPath:@"loggingIn"options:NSKeyValueObservingOptionInitialcontext:&ObservationContext];[self.usernameTextFieldaddTarget:selfaction:@selector(updateLogInButton)forControlEvents:UIControlEventEditingChanged];[self.passwordTextFieldaddTarget:selfaction:@selector(updateLogInButton)forControlEvents:UIControlEventEditingChanged];}-(void)updateLogInButton{BOOLtextFieldsNonEmpty=self.usernameTextField.text.length>0&&self.passwordTextField.text.length>0;BOOLreadyToLogIn=!LoginManager.sharedManager.isLoggingIn&&!self.loggedIn;self.logInButton.enabled=textFieldsNonEmpty&&readyToLogIn;}-(void)observeValueForKeyPath:(NSString*)keyPathofObject:(id)objectchange:(NSDictionary*)changecontext:(void*)context{if(context==ObservationContext){[selfupdateLogInButton];}else{[superobserveValueForKeyPath:keyPathofObject:objectchange:changecontext:context];}}
RAC通過引入信號(hào)(Signal)的概念尖淘,來代替?zhèn)鹘y(tǒng)iOS開發(fā)中對(duì)于控件狀態(tài)變化檢查的代理(delegate)模式或target-action模式奕锌。因?yàn)镽AC的信號(hào)是可以組合(combine)的,所以可以輕松地構(gòu)造出另一個(gè)新的信號(hào)出來村生,然后將按鈕的enabled狀態(tài)與新的信號(hào)綁定惊暴。如下所示:
123456789
RAC(self.logInButton,enabled)=[RACSignalcombineLatest:@[self.usernameTextField.rac_textSignal,self.passwordTextField.rac_textSignal,RACObserve(LoginManager.sharedManager,loggingIn),RACObserve(self,loggedIn)]reduce:^(NSString*username,NSString*password,NSNumber*loggingIn,NSNumber*loggedIn){return@(username.length>0&&password.length>0&&!loggingIn.boolValue&&!loggedIn.boolValue);}];
可以看到,在引入RAC之后趁桃,以前散落在action-target或KVO的回調(diào)函數(shù)中的判斷邏輯被統(tǒng)一到了一起辽话,從而使得登錄按鈕的enabled狀態(tài)被更加清晰地表達(dá)了出來。
除了組合(combine)之外卫病,RAC的信號(hào)還支持鏈?zhǔn)剑╟haining)和過濾(filter)油啤,以方便將信號(hào)進(jìn)行進(jìn)一步處理。
試圖解決MVC框架的問題
對(duì)于傳統(tǒng)的Model-View-Controller的框架蟀苛,Controller很容易變得比較龐大和復(fù)雜益咬。由于Controller承擔(dān)了Model和View之間的橋梁作用,所以Controller常常與對(duì)應(yīng)的View和Model的耦合度非常高帜平,這同時(shí)也造成對(duì)其做單元測(cè)試非常不容易幽告,對(duì)iOS工程的單元測(cè)試大多都只在一些工具類或與界面無關(guān)的邏輯類中進(jìn)行。
RAC的信號(hào)機(jī)制很容易將某一個(gè)Model變量的變化與界面關(guān)聯(lián)罕模,所以非常容易應(yīng)用Model-View-ViewModel框架评腺。通過引入ViewModel層,然后用RAC將ViewModel與View關(guān)聯(lián)淑掌,View層的變化可以直接響應(yīng)ViewModel層的變化蒿讥,這使得Controller變得更加簡單,由于View不再與Model綁定抛腕,也增加了View的可重用性芋绸。
因?yàn)橐肓薞iewModel層,所以單元測(cè)試可以在ViewModel層進(jìn)行担敌,iOS工程的可測(cè)試性也大大增強(qiáng)了摔敛。InfoQ也曾撰文介紹過MVVM:《MVVM啟示錄》。
統(tǒng)一消息傳遞機(jī)制
iOS開發(fā)中有著各種消息傳遞機(jī)制全封,包括KVO马昙、Notification桃犬、delegation、block以及target-action方式行楞。各種消息傳遞機(jī)制使得開發(fā)者在做具體選擇時(shí)感到困惑攒暇,例如在objc.io上就有專門撰文(破船的翻譯),介紹各種消息傳遞機(jī)制之間的差異性子房。
RAC將傳統(tǒng)的UI控件事件進(jìn)行了封裝形用,使得以上各種消息傳遞機(jī)制都可以用RAC來完成。示例代碼如下:
123456789101112131415161718192021222324
// KVO[RACObserve(self,username)subscribeNext:^(idx){NSLog(@"成員變量 username 被修改成了:%@",x);}];// target-actionself.button.rac_command=[[RACCommandalloc]initWithSignalBlock:^RACSignal*(idinput){NSLog(@"按鈕被點(diǎn)擊");return[RACSignalempty];}];// Notification[[[NSNotificationCenterdefaultCenter]rac_addObserverForName:UIKeyboardDidChangeFrameNotificationobject:nil]subscribeNext:^(idx){NSLog(@"鍵盤Frame改變");}];// Delegate[[selfrac_signalForSelector:@selector(viewWillAppear:)]subscribeNext:^(idx){debugLog(@"viewWillAppear方法被調(diào)用 %@",x);}];
RAC的RACSignal類也提供了createSignal方法來讓用戶創(chuàng)建自定義的信號(hào)证杭,如下代碼創(chuàng)建了一個(gè)下載指定網(wǎng)站內(nèi)容的信號(hào)田度。
12345678910111213141516171819
-(RACSignal*)urlResults{return[RACSignalcreateSignal:^RACDisposable*(idsubscriber){NSError*error;NSString*result=[NSStringstringWithContentsOfURL:[NSURLURLWithString:@"http://www.devtang.com"]encoding:NSUTF8StringEncodingerror:&error];NSLog(@"download");if(!result){[subscribersendError:error];}else{[subscribersendNext:result];[subscribersendCompleted];}return[RACDisposabledisposableWithBlock:^{NSLog(@"clean up");}];}];}
如何使用ReactiveCocoa
ReactiveCocoa可以在iOS和OS X的應(yīng)用開發(fā)中使用,對(duì)于iOS開發(fā)者解愤,可以將RAC源碼下載編譯后镇饺,使用編譯好的libReactiveCocoa-iOS.a文件。
開發(fā)者也可以用CocoaPods來設(shè)置目標(biāo)工程對(duì)ReactiveCocoa的依賴琢歇,只需要編輯Podfile文件兰怠,增加如下內(nèi)容即可:
1
pod'ReactiveCocoa'
ReactiveCocoa的特點(diǎn)
RAC在應(yīng)用中大量使用了block,由于Objective-C語言的內(nèi)存管理是基于引用計(jì)數(shù)的李茫,為了避免循環(huán)引用問題揭保,在block中如果要引用self,需要使用@weakify(self)和@strongify(self)來避免強(qiáng)引用魄宏。另外秸侣,在使用時(shí)應(yīng)該注意block的嵌套層數(shù),不恰當(dāng)?shù)臑E用多層嵌套block可能給程序的可維護(hù)性帶來災(zāi)難宠互。
RAC的編程方式和傳統(tǒng)的MVC方式差異巨大味榛,所以需要較長的學(xué)習(xí)時(shí)間。并且予跌,業(yè)界內(nèi)對(duì)于RAC并沒有廣泛應(yīng)用搏色,這造成可供參考的項(xiàng)目和教程比較欠缺。 另外券册,RAC項(xiàng)目本身也還在快速演進(jìn)當(dāng)中频轿,1.x版本和2.x版本API改動(dòng)了許多,3.0版本也正在快速開發(fā)中烁焙,對(duì)它的使用也需要考慮后期的升級(jí)維護(hù)問題航邢。
作為一個(gè)iOS開發(fā)領(lǐng)域的新開源框架,ReactiveCocoa帶來了函數(shù)式編程和響應(yīng)式編程的思想骄蝇,值得大家關(guān)注并且學(xué)習(xí)膳殷。
一些學(xué)習(xí)資源
博客&教程
http://spin.atomicobject.com/2014/02/03/objective-c-delegate-pattern/
http://blog.bignerdranch.com/4549-data-driven-ios-development-reactivecocoa/
http://en.wikipedia.org/wiki/Functional_reactive_programming
http://www.teehanlax.com/blog/reactivecocoa/
http://www.teehanlax.com/blog/getting-started-with-reactivecocoa/
http://nshipster.com/reactivecocoa/
http://cocoasamurai.blogspot.com/2013/03/basic-mvvm-with-reactivecocoa.html
http://iiiyu.com/2013/09/11/learning-ios-notes-twenty-eight/
https://speakerdeck.com/andrewsardone/reactivecocoa-at-mobidevday-2013
http://msdn.microsoft.com/en-us/library/hh848246.aspx
http://blog.leezhong.com/ios/2013/12/27/reactivecocoa-2.html
https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/Documentation/FrameworkOverview.md
http://www.haskell.org/haskellwiki/Functional_Reactive_Programming
http://blog.zhaojie.me/2009/09/functional-reactive-programming-for-csharp.html
代碼
https://github.com/Machx/MVVM-IOS-Example
https://github.com/ReactiveCocoa/RACiOSDemo
書籍
視頻