什么是ReactiveCocoa
ReactiveCocoa(其簡稱為RAC)是由Github開源的一個應用于iOS和OS X開發的新框架。RAC具有函數式編程和響應式編程的特性。它主要吸取了.Net的Reactive Extensions的設計和實現。
ReactiveCocoa試圖解決什么問題
經過一段時間的研究,我認為ReactiveCocoa試圖解決以下3個問題:
傳統iOS開發過程中,狀態以及狀態之間依賴過多的問題
傳統MVC架構的問題:Controller比較復雜,可測試性差
提供統一的消息傳遞機制
傳統iOS開發過程中,狀態以及狀態之間依賴過多的問題
我們在開發iOS應用時,一個界面元素的狀態很可能受多個其它界面元素或后臺狀態的影響。
例如,在用戶帳戶的登錄界面,通常會有2個輸入框(分別輸入帳號和密碼)和一個登錄按鈕。如果我們要加入一個限制條件:當用戶輸入完帳號和密碼,并且登錄的網絡請求還未發出時,確定按鈕才可以點擊。通常情況下,我們需要監聽這兩個輸入框的狀態變化以及登錄的網絡請求狀態,然后修改另一個控件的enabled狀態。
傳統的寫法如下(該示例代碼修改自ReactiveCocoa官網) :
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通過引入信號(Signal)的概念,來代替傳統iOS開發中對于控件狀態變化檢查的代理(delegate)模式或target-action模式。因為RAC的信號是可以組合(combine)的,所以可以輕松地構造出另一個新的信號出來,然后將按鈕的enabled狀態與新的信號綁定。如下所示:
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的回調函數中的判斷邏輯被統一到了一起,從而使得登錄按鈕的enabled狀態被更加清晰地表達了出來。
除了組合(combine)之外,RAC的信號還支持鏈式(chaining)和過濾(filter),以方便將信號進行進一步處理。
試圖解決MVC框架的問題
對于傳統的Model-View-Controller的框架,Controller很容易變得比較龐大和復雜。由于Controller承擔了Model和View之間的橋梁作用,所以Controller常常與對應的View和Model的耦合度非常高,這同時也造成對其做單元測試非常不容易,對iOS工程的單元測試大多都只在一些工具類或與界面無關的邏輯類中進行。
RAC的信號機制很容易將某一個Model變量的變化與界面關聯,所以非常容易應用Model-View-ViewModel框架。通過引入ViewModel層,然后用RAC將ViewModel與View關聯,View層的變化可以直接響應ViewModel層的變化,這使得Controller變得更加簡單,由于View不再與Model綁定,也增加了View的可重用性。
因為引入了ViewModel層,所以單元測試可以在ViewModel層進行,iOS工程的可測試性也大大增強了。InfoQ也曾撰文介紹過MVVM:《MVVM啟示錄》。
統一消息傳遞機制
iOS開發中有著各種消息傳遞機制,包括KVO、Notification、delegation、block以及target-action方式。各種消息傳遞機制使得開發者在做具體選擇時感到困惑,例如在objc.io上就有專門撰文(破船的翻譯),介紹各種消息傳遞機制之間的差異性。
RAC將傳統的UI控件事件進行了封裝,使得以上各種消息傳遞機制都可以用RAC來完成。示例代碼如下:
123456789101112131415161718192021222324
// KVO[RACObserve(self,username)subscribeNext:^(idx){NSLog(@"成員變量 username 被修改成了:%@",x);}];// target-actionself.button.rac_command=[[RACCommandalloc]initWithSignalBlock:^RACSignal*(idinput){NSLog(@"按鈕被點擊");return[RACSignalempty];}];// Notification[[[NSNotificationCenterdefaultCenter]rac_addObserverForName:UIKeyboardDidChangeFrameNotificationobject:nil]subscribeNext:^(idx){NSLog(@"鍵盤Frame改變");}];// Delegate[[selfrac_signalForSelector:@selector(viewWillAppear:)]subscribeNext:^(idx){debugLog(@"viewWillAppear方法被調用 %@",x);}];
RAC的RACSignal類也提供了createSignal方法來讓用戶創建自定義的信號,如下代碼創建了一個下載指定網站內容的信號。
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的應用開發中使用,對于iOS開發者,可以將RAC源碼下載編譯后,使用編譯好的libReactiveCocoa-iOS.a文件。
開發者也可以用CocoaPods來設置目標工程對ReactiveCocoa的依賴,只需要編輯Podfile文件,增加如下內容即可:
1
pod'ReactiveCocoa'
ReactiveCocoa的特點
RAC在應用中大量使用了block,由于Objective-C語言的內存管理是基于引用計數的,為了避免循環引用問題,在block中如果要引用self,需要使用@weakify(self)和@strongify(self)來避免強引用。另外,在使用時應該注意block的嵌套層數,不恰當的濫用多層嵌套block可能給程序的可維護性帶來災難。
RAC的編程方式和傳統的MVC方式差異巨大,所以需要較長的學習時間。并且,業界內對于RAC并沒有廣泛應用,這造成可供參考的項目和教程比較欠缺。 另外,RAC項目本身也還在快速演進當中,1.x版本和2.x版本API改動了許多,3.0版本也正在快速開發中,對它的使用也需要考慮后期的升級維護問題。
作為一個iOS開發領域的新開源框架,ReactiveCocoa帶來了函數式編程和響應式編程的思想,值得大家關注并且學習。
一些學習資源
博客&教程
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
書籍
視頻