1 代碼開發(fā)實(shí)戰(zhàn)
1.1 入門講解
ReactiveCocoa入門教程:第一部分
http://www.cocoachina.com/ios/20150123/10994.html
1.1.1 事件流控制rac_textSignal
????????ReactiveCocoa有很多操作來控制事件流。假設(shè)你只關(guān)心超過3個字符長度的用戶名,那么你可以使用filter操作來實(shí)現(xiàn)這個目的。把之前加在viewDidLoad中的代碼更新成下面的:
[[self.usernameTextField.rac_textSignal filter: ^BOOL(id?value){
????NSString*text?=?value;
???return?text.length?>?3;
}] subscribeNext: ^(id?x){
???NSLog(@"%@",?x);
}];
????????編譯運(yùn)行,在text field只能怪輸入幾個字,你會發(fā)現(xiàn)只有當(dāng)輸入超過3個字符時才會有l(wèi)og。
2013-12-26?08:17:51.335?RWReactivePlayground[9654:a0b]?is?t
2013-12-26?08:17:51.478?RWReactivePlayground[9654:a0b]?is?th
2013-12-26?08:17:51.526?RWReactivePlayground[9654:a0b]?is?thi
2013-12-26?08:17:51.548?RWReactivePlayground[9654:a0b]?is?this
2013-12-26?08:17:51.676?RWReactivePlayground[9654:a0b]?is?this
2013-12-26?08:17:51.798?RWReactivePlayground[9654:a0b]?is?thism
2013-12-26?08:17:51.926?RWReactivePlayground[9654:a0b]?is?thisma
2013-12-26?08:17:51.987?RWReactivePlayground[9654:a0b]?is?thismag
2013-12-26?08:17:52.141?RWReactivePlayground[9654:a0b]?is?thismagi
2013-12-26?08:17:52.229?RWReactivePlayground[9654:a0b]?is?thismagic
2013-12-26?08:17:52.486?RWReactivePlayground[9654:a0b]?is?thismagic?
????????剛才所創(chuàng)建的只是一個很簡單的管道。這就是響應(yīng)式編程的本質(zhì),根據(jù)數(shù)據(jù)流來表達(dá)應(yīng)用的功能。用圖形來表達(dá)就是下面這樣的:

????????從上面的圖中可以看到,rac_textSignal是起始事件。然后數(shù)據(jù)通過一個filter,如果這個事件包含一個長度超過3的字符串,那么該事件就可以通過。管道的最后一步就是subscribeNext:,block在這里打印出事件的值。
????????filter操作的輸出也是RACSignal,這點(diǎn)先放到一邊。你可以像下面那樣調(diào)整一下代碼來展示每一步的操作。
RACSignal?*usernameSourceSignal?= self.usernameTextField.rac_textSignal;
RACSignal?*filteredUsername?= [usernameSourceSignal filter: ^BOOL(id?value){
????NSString*text?=?value;
????return?text.length?>?3;
}];
[filteredUsername?subscribeNext: ^(id?x){
??????NSLog(@"%@",?x);
}];
????????RACSignal的每個操作都會返回一個RACsignal,這在術(shù)語上叫做連貫接口(fluent interface)。這個功能可以讓你直接構(gòu)建管道,而不用每一步都使用本地變量。
????????注意:ReactiveCocoa大量使用block。如果你是block新手,你可能想看看Apple官方的block編程指南。如果你熟悉block,但是覺得block的語法有些奇怪和難記,你可能會想看看這個有趣又實(shí)用的網(wǎng)頁f*****gblocksyntax.com。
1.1.2 類型轉(zhuǎn)換filter(數(shù)據(jù)過濾)
????????如果你之前把代碼分成了多個步驟,現(xiàn)在再把它改回來吧。
[[self.usernameTextField.rac_textSignal filter: ^BOOL(id?value){
????NSString*text?=?value;?//?implicit?cast
????return?text.length?>?3;
}] subscribeNext: ^(id?x){
????NSLog(@"%@",?x);
}];
????????在上面的代碼中,注釋部分標(biāo)記了將id隱式轉(zhuǎn)換為NSString,這看起來不是很好看。幸運(yùn)的是,傳入block的值肯定是個NSString,所以你可以直接修改參數(shù)類型,把代碼更新成下面的這樣的:
[[self.usernameTextField.rac_textSignal filter: ^BOOL(NSString *text){
????return?text.length?>?3;
}] subscribeNext: ^(id?x){
????NSLog(@"%@",?x);
}];
????????編譯運(yùn)行,確保沒什么問題。
1.1.3 map(數(shù)據(jù)傳遞轉(zhuǎn)換)
? ??????什么是事件呢?
????????到目前為止,本篇教程已經(jīng)描述了不同的事件類型,但是還沒有說明這些事件的結(jié)構(gòu)。有意思的是(?),事件可以包括任何事情。下面來展示一下,在管道中添加另一個操作。把添加在viewDidLoad中的代碼更新成下面的:
[[[self.usernameTextField.rac_textSignal map: ^id(NSString *text){
????return?@(text.length);
}] filter: ^BOOL(NSNumber *length){
????return?[length?integerValue]?>?3;
}] subscribeNext: ^(id?x){
????NSLog(@"%@",?x);
}];
????????編譯運(yùn)行,你會發(fā)現(xiàn)log輸出變成了文本的長度而不是內(nèi)容。
2013-12-26?12:06:54.566?RWReactivePlayground[10079:a0b]?4
2013-12-26?12:06:54.725?RWReactivePlayground[10079:a0b]?5
2013-12-26?12:06:54.853?RWReactivePlayground[10079:a0b]?6
2013-12-26?12:06:55.061?RWReactivePlayground[10079:a0b]?7
2013-12-26?12:06:55.197?RWReactivePlayground[10079:a0b]?8
2013-12-26?12:06:55.300?RWReactivePlayground[10079:a0b]?9
2013-12-26?12:06:55.462?RWReactivePlayground[10079:a0b]?10
2013-12-26?12:06:55.558?RWReactivePlayground[10079:a0b]?11
2013-12-26?12:06:55.646?RWReactivePlayground[10079:a0b]?12
????????新加的map操作通過block改變了事件的數(shù)據(jù)。map從上一個next事件接收數(shù)據(jù),通過執(zhí)行block把返回值傳給下一個next事件。在上面的代碼中,map以NSString為輸入,取字符串的長度,返回一個NSNumber。
????????來看下面的圖片:

????????能看到map操作之后的步驟收到的都是NSNumber實(shí)例。你可以使用map操作來把接收的數(shù)據(jù)轉(zhuǎn)換成想要的類型,只要它是個對象。
????????注意:在上面的例子中text.length返回一個NSUInteger,是一個基本類型。為了將它作為事件的內(nèi)容,NSUInteger必須被封裝。幸運(yùn)的是Objective-C literal syntax提供了一種簡單的方法來封裝——@ (text.length)。
????????現(xiàn)在差不多是時候用所學(xué)的內(nèi)容來更新一下ReactivePlayground應(yīng)用了。你可以把之前的添加代碼都刪除了。
1.1.4 創(chuàng)建有效狀態(tài)信號RACSignal
????????首先要做的就是創(chuàng)建一些信號,來表示用戶名和密碼輸入框中的輸入內(nèi)容是否有效。把下面的代碼添加到RWViewController.m中viewDidLoad的最后面:
RACSignal?*validUsernameSignal?= [self.usernameTextField.rac_textSignal map: ^id(NSString?*text)?{
????return?@([self?isValidUsername: text]);
}];
RACSignal?*validPasswordSignal?= [self.passwordTextField.rac_textSignal map: ^id(NSString?*text)?{
?????return?@([self?isValidPassword: text]);
}];
????????可以看到,上面的代碼對每個輸入框的rac_textSignal應(yīng)用了一個map轉(zhuǎn)換。輸出是一個用NSNumber封裝的布爾值。
????????下一步是轉(zhuǎn)換這些信號,從而能為輸入框設(shè)置不同的背景顏色?;旧暇褪牵阌嗛嗊@些信號,然后用接收到的值來更新輸入框的背景顏色。下面有一種方法:
[[validPasswordSignal map: ^id(NSNumber?*passwordValid){
????return?[passwordValid?boolValue]???[UIColor?clearColor]:[UIColor?yellowColor];
}] subscribeNext: ^(UIColor?*color){
????self.passwordTextField.backgroundColor?=?color;
}];
????????(不要使用這段代碼,下面有一種更好的寫法?。?/p>
????????從概念上來說,就是把之前信號的輸出應(yīng)用到輸入框的backgroundColor屬性上。但是上面的用法不是很好。
????????幸運(yùn)的是,ReactiveCocoa提供了一個宏來更好的完成上面的事情。把下面的代碼直接加到viewDidLoad中兩個信號的代碼后面:
RAC(self.passwordTextField,?backgroundColor)?= [validPasswordSignal map: ^id(NSNumber?*passwordValid){
??????return?[passwordValid?boolValue]???[UIColor?clearColor]:[UIColor?yellowColor];
}];
RAC(self.usernameTextField,?backgroundColor)?= [validUsernameSignal map: ^id(NSNumber?*passwordValid){
?????return?[passwordValid?boolValue]???[UIColor?clearColor]:[UIColor?yellowColor];
}];
????????RAC宏允許直接把信號的輸出應(yīng)用到對象的屬性上。RAC宏有兩個參數(shù),第一個是需要設(shè)置屬性值的對象,第二個是屬性名。每次信號產(chǎn)生一個next事件,傳遞過來的值都會應(yīng)用到該屬性上。
????????你不覺得這種方法很好嗎?
????????在編譯運(yùn)行之前,找到updateUIState方法,把頭兩行刪掉。
self.usernameTextField.backgroundColor?= self.usernameIsValid???[UIColor?clearColor]?:?[UIColor?yellowColor];
self.passwordTextField.backgroundColor?= self.passwordIsValid???[UIColor?clearColor]?:?[UIColor?yellowColor];
????????這樣就把不相關(guān)的代碼刪掉了。
????????編譯運(yùn)行,可以發(fā)現(xiàn)當(dāng)輸入內(nèi)容無效時,輸入框看起來高亮了,有效時又透明了。
????????現(xiàn)在的邏輯用圖形來表示就是下面這樣的。能看到有兩條簡單的管道,兩個文本信號,經(jīng)過一個map轉(zhuǎn)為表示是否有效的布爾值,再經(jīng)過一個map轉(zhuǎn)為UIColor,而這個UIColor已經(jīng)和輸入框的背景顏色綁定了。

????????你是否好奇為什么要創(chuàng)建兩個分開的validPasswordSignal和validUsernameSignal呢,而不是每個輸入框一個單獨(dú)的管道呢?(?)稍安勿躁,答案就在下面。
????????原文:Are you wondering why you created separate validPasswordSignal and validUsernameSignal signals, as opposed to a single fluent pipeline for each text field? Patience dear reader, the method behind this madness will become clear shortly!
1.1.5 聚合信號combineLatest
????????目前在應(yīng)用中,登錄按鈕只有當(dāng)用戶名和密碼輸入框的輸入都有效時才工作?,F(xiàn)在要把這里改成響應(yīng)式的。
????????現(xiàn)在的代碼中已經(jīng)有可以產(chǎn)生用戶名和密碼輸入框是否有效的信號了——validUsernameSignal和validPasswordSignal了?,F(xiàn)在需要做的就是聚合這兩個信號來決定登錄按鈕是否可用。
????????把下面的代碼添加到viewDidLoad的末尾:
RACSignal?*signUpActiveSignal?= [RACSignalcombineLatest: @[validUsernameSignal,?validPasswordSignal] reduce: ^id(NSNumber *usernameValid,?NSNumber?*passwordValid){
? ??return?@([usernameValid?boolValue]&&[passwordValid?boolValue]);
}];
????????上面的代碼使用combineLatest:reduce:方法把validUsernameSignal和validPasswordSignal產(chǎn)生的最新的值聚合在一起,并生成一個新的信號。每次這兩個源信號的任何一個產(chǎn)生新值時,reduce
block都會執(zhí)行,block的返回值會發(fā)給下一個信號。
????????注意:RACsignal的這個方法可以聚合任意數(shù)量的信號,reduce block的參數(shù)和每個源信號相關(guān)。ReactiveCocoa有一個工具類RACBlockTrampoline,它在內(nèi)部處理reduce block的可變參數(shù)。實(shí)際上在ReactiveCocoa的實(shí)現(xiàn)中有很多隱藏的技巧,值得你去看看。
????????現(xiàn)在已經(jīng)有了合適的信號,把下面的代碼添加到viewDidLoad的末尾。這會把信號和按鈕的enabled屬性綁定。
[signUpActiveSignal?subscribeNext: ^(NSNumber *signupActive){
????self.signInButton.enabled?= [signupActive?boolValue];
}];
????????在運(yùn)行之前,把以前的舊實(shí)現(xiàn)刪掉。把下面這兩個屬性刪掉。
@property?(nonatomic)?BOOL?passwordIsValid;
@property?(nonatomic)?BOOL?usernameIsValid;
????????把viewDidLoad中的這些也刪掉:
//?handle?text?changes?for?both?text?fields
[self.usernameTextField?addTarget: self action: @selector(usernameTextFieldChanged)
forControlEvents: UIControlEventEditingChanged];
[self.passwordTextField?addTarget: self action: @selector(passwordTextFieldChanged)
forControlEvents: UIControlEventEditingChanged];
????????同樣把updateUIState、usernameTextFieldChanged和passwordTextFieldChanged方法刪掉。
最后確保把viewDidLoad中updateUIState的調(diào)用刪掉。
????????編譯運(yùn)行,看看登錄按鈕。當(dāng)用戶名和密碼輸入有效時,按鈕就是可用的,和以前一樣。
????????現(xiàn)在應(yīng)用的邏輯就是下面這樣的:

????????上圖展示了一些重要的概念,你可以使用ReactiveCocoa來完成一些重量級的任務(wù)。
? 分割——信號可以有很多subscriber,也就是作為很多后續(xù)步驟的源。注意上圖中那個用來表示用戶名和密碼有效性的布爾信號,它被分割成多個,用于不同的地方。
? 聚合——多個信號可以聚合成一個新的信號,在上面的例子中,兩個布爾信號聚合成了一個。實(shí)際上你可以聚合并產(chǎn)生任何類型的信號。
????????這些改動的結(jié)果就是,代碼中沒有用來表示兩個輸入框有效狀態(tài)的私有屬性了。這就是用響應(yīng)式編程的一個關(guān)鍵區(qū)別,你不需要使用實(shí)例變量來追蹤瞬時狀態(tài)。
1.1.6 應(yīng)用實(shí)例——響應(yīng)式的登錄
1.1.6.1 創(chuàng)建界面
????????應(yīng)用目前使用上面圖中展示的響應(yīng)式管道來管理輸入框和按鈕的狀態(tài)。但是按鈕按下的處理用的還是action,所以下一步就是把剩下的邏輯都替換成響應(yīng)式的。
????????在storyboard中,登錄按鈕的Touch Up Inside事件和RWViewController.m中的signInButtonTouched方法是綁定的。下面會用響應(yīng)的方法替換,所以首先要做的就是斷開當(dāng)前的storyboard action。
????????打開Main.storyboard,找到登錄按鈕,按住ctrl鍵單擊,打開outlet/action連接框,然后點(diǎn)擊x來斷開連接。如果你找不到的話,下圖中紅色箭頭指示的就是刪除按鈕。

????????你已經(jīng)知道了ReactiveCocoa框架是如何給基本UIKit控件添加屬性和方法的了。目前你已經(jīng)使用了rac_textSignal,它會在文本發(fā)生變化時產(chǎn)生信號。為了處理按鈕的事件,現(xiàn)在需要用到ReactiveCocoa為UIKit添加的另一個方法,rac_signalForControlEvents。
????????現(xiàn)在回到RWViewController.m,把下面的代碼添加到viewDidLoad的末尾:
[[self.signInButton rac_signalForControlEvents: UIControlEventTouchUpInside] subscribeNext: ^(id?x)?{
?????NSLog(@"button?clicked");
}];
????????上面的代碼從按鈕的UIControlEventTouchUpInside事件創(chuàng)建了一個信號,然后添加了一個訂閱,在每次事件發(fā)生時都會輸出log。
????????編譯運(yùn)行,確保的確有l(wèi)og輸出。按鈕只在用戶名和密碼框輸入有效時可用,所以在點(diǎn)擊按鈕前需要在兩個文本框中輸入一些內(nèi)容。
????????可以看到Xcode控制臺的輸出和下面的類似:
2013-12-28?08:05:10.816?RWReactivePlayground[18203:a0b]?button?clicked
2013-12-28?08:05:11.675?RWReactivePlayground[18203:a0b]?button?clicked
2013-12-28?08:05:12.605?RWReactivePlayground[18203:a0b]?button?clicked
2013-12-28?08:05:12.766?RWReactivePlayground[18203:a0b]?button?clicked
2013-12-28?08:05:12.917?RWReactivePlayground[18203:a0b]?button?clicked
????????現(xiàn)在按鈕有了點(diǎn)擊事件的信號,下一步就是把它和登錄流程連接起來。那么問題就來了,打開RWDummySignInService.h,看一下接口:
typedef?void?(^RWSignInResponse)(BOOL);
@interface?RWDummySignInService?:?NSObject
-?(void) signInWithUsername: (NSString?*)username password: (NSString?*)password complete: (RWSignInResponse)completeBlock;
@end
????????這個service有3個參數(shù),用戶名、密碼和一個完成回調(diào)block。這個block會在登錄成功或失敗時執(zhí)行。你可以在按鈕點(diǎn)擊事件的subscribeNext: blcok里直接調(diào)用這個方法,但是為什么你要這么做?
????????注意:本教程為了簡便使用了一個假的service,所以它不依賴任何外部API。但你現(xiàn)在的確遇到了一個問題,如何使用這些不是用信號表示的API呢?
1.1.6.2 創(chuàng)建信號
????????幸運(yùn)的是,把已有的異步API用信號的方式來表示相當(dāng)簡單。首先把RWViewController.m中的signInButtonTouched:刪掉。你會用響應(yīng)式的的方法來替換這段邏輯。
????????還是在RWViewController.m中,添加下面的方法:
-?(RACSignal?*) signInSignal?{
? ??return?[RACSignal?createSignal: ^RACDisposable?*(id?subscriber){
????????[self.signInService signInWithUsername: self.usernameTextField.text password: self.passwordTextField.text complete: ^(BOOL?success){
????????????[subscriber?sendNext: @(success)];
????????????[subscriber?sendCompleted];
????????}];
???????return?nil;
????}];
}
????????上面的方法創(chuàng)建了一個信號,使用用戶名和密碼登錄。現(xiàn)在分解來看一下。
????????上面的代碼使用RACSignal的createSignal:方法來創(chuàng)建信號。方法的入?yún)⑹且粋€block,這個block描述了這個信號。當(dāng)這個信號有subscriber時,block里的代碼就會執(zhí)行。????????
????????block的入?yún)⑹且粋€subscriber實(shí)例,它遵循RACSubscriber協(xié)議,協(xié)議里有一些方法來產(chǎn)生事件,你可以發(fā)送任意數(shù)量的next事件,或者用error\complete事件來終止。本例中,信號發(fā)送了一個next事件來表示登錄是否成功,隨后是一個complete事件。
????????這個block的返回值是一個RACDisposable對象,它允許你在一個訂閱被取消時執(zhí)行一些清理工作。當(dāng)前的信號不需要執(zhí)行清理操作,所以返回nil就可以了。
????????可以看到,把一個異步API用信號封裝是多簡單!
????????現(xiàn)在就來使用這個新的信號。把之前添加在viewDidLoad中的代碼更新成下面這樣的:
[[[self.signInButton rac_signalForControlEvents: UIControlEventTouchUpInside] map: ^id(id?x){
?????return?[self?signInSignal];
}] subscribeNext: ^(id?x){
?????NSLog(@"Sign?in?result:?%@",?x);
}];
????????上面的代碼使用map方法,把按鈕點(diǎn)擊信號轉(zhuǎn)換成了登錄信號。subscriber輸出log。
????????編譯運(yùn)行,點(diǎn)擊登錄按鈕,查看Xcode的控制臺,等等,輸出的這是個什么鬼?
2014-01-08?21:00:25.919?RWReactivePlayground[33818:a0b]?Sign?inresult:
name:?+createSignal:
????????沒錯,你已經(jīng)給subscribeNext:的block傳入了一個信號,但傳入的不是登錄結(jié)果的信號。
????????下圖展示了到底發(fā)生了什么:

????????當(dāng)點(diǎn)擊按鈕時,rac_signalForControlEvents發(fā)送了一個next事件(事件的data是UIButton)。map操作創(chuàng)建并返回了登錄信號,這意味著后續(xù)步驟都會收到一個RACSignal。這就是你在subscribeNext:這步看到的。
????????上面問題的解決方法,有時候叫做信號中的信號,換句話說就是一個外部信號里面還有一個內(nèi)部信號。你可以在外部信號的subscribeNext:block里訂閱內(nèi)部信號。不過這樣嵌套太混亂啦,還好ReactiveCocoa已經(jīng)解決了這個問題。
1.1.6.3 信號中的信號flattenMap
????????解決的方法很簡單,只需要把map操作改成flattenMap就可以了:
[[[self.signInButton rac_signalForControlEvents: UIControlEventTouchUpInside] flattenMap: ^id(id?x){
?????return?[self?signInSignal];
}] subscribeNext: ^(id?x){
?????NSLog(@"Sign?in?result:?%@",?x);
}];
????????這個操作把按鈕點(diǎn)擊事件轉(zhuǎn)換為登錄信號,同時還從內(nèi)部信號發(fā)送事件到外部信號。
????????編譯運(yùn)行,注意控制臺,現(xiàn)在應(yīng)該輸出登錄是否成功了。
2013-12-28?18:20:08.156?RWReactivePlayground[22993:a0b]?Sign?inresult:?0
2013-12-28?18:25:50.927?RWReactivePlayground[22993:a0b]?Sign?inresult:?1
????????還不錯。
????????現(xiàn)在已經(jīng)完成了大部分的內(nèi)容,最后就是在subscribeNext步驟里添加登錄成功后跳轉(zhuǎn)的邏輯。把代碼更新成下面的:
[[[self.signInButton rac_signalForControlEvents: UIControlEventTouchUpInside] flattenMap: ^id(id?x){
???return?[self?signInSignal];
}] subscribeNext: ^(NSNumber*signedIn){
????BOOL?success?=[signedIn?boolValue];
????self.signInFailureText.hidden?=?success;
???if?(success){
? ? ????[self?performSegueWithIdentifier: @"signInSuccess" sender: self];
????}
}];
????????subscribeNext: block從登錄信號中取得結(jié)果,相應(yīng)地更新signInFailureText是否可見。如果登錄成功執(zhí)行導(dǎo)航跳轉(zhuǎn)。
????????編譯運(yùn)行,應(yīng)該就能再看到可愛的小貓啦!喵~

????????你注意到這個應(yīng)用現(xiàn)在有一些用戶體驗(yàn)上的小問題了嗎?當(dāng)?shù)卿泂ervice正在校驗(yàn)用戶名和密碼時,登錄按鈕應(yīng)該是不可點(diǎn)擊的。這會防止用戶多次執(zhí)行登錄操作。還有,如果登錄失敗了,用戶再次嘗試登錄時,應(yīng)該隱藏錯誤信息。
????????這個邏輯應(yīng)該怎么添加呢?改變按鈕的可用狀態(tài)并不是轉(zhuǎn)換(map)、過濾(filter)或者其他已經(jīng)學(xué)過的概念。其實(shí)這個就叫做“副作用”,換句話說就是在一個next事件發(fā)生時執(zhí)行的邏輯,而該邏輯并不改變事件本身。
1.1.6.4 添加附加操作(Adding side-effects)
????????把代碼更新成下面的:
[[[[self.signInButton rac_signalForControlEvents: UIControlEventTouchUpInside] doNext: ^(id?x){
????self.signInButton.enabled?=NO;
????self.signInFailureText.hidden?=YES;
}] flattenMap: ^id(id?x){
?????return?[self?signInSignal];
}] subscribeNext: ^(NSNumber *signedIn){
????self.signInButton.enabled?= YES;
????BOOL?success?= [signedIn?boolValue];
????self.signInFailureText.hidden?=?success;
????if?(success){
???????[self?performSegueWithIdentifier: @"signInSuccess" sender: self];
????}
}];
????????你可以看到doNext:是直接跟在按鈕點(diǎn)擊事件的后面。而且doNext: block并沒有返回值。因?yàn)樗歉郊硬僮?,并不改變事件本身?/p>
????????上面的doNext: block把按鈕置為不可點(diǎn)擊,隱藏登錄失敗提示。然后在subscribeNext: block里重新把按鈕置為可點(diǎn)擊,并根據(jù)登錄結(jié)果來決定是否顯示失敗提示。
????????之前的管道圖就更新成下面這樣的:

????????編譯運(yùn)行,確保登錄按鈕的可點(diǎn)擊狀態(tài)和預(yù)期的一樣?,F(xiàn)在所有的工作都已經(jīng)完成了,這個應(yīng)用已經(jīng)是響應(yīng)式的啦。你中途哪里出了問題,可以下載最終的工程(依賴庫都有),或者在Github上找到這份代碼,教程中的每一次編譯運(yùn)行都有對應(yīng)的commit。
????????注意:在異步操作執(zhí)行的過程中禁用按鈕是一個常見的問題,ReactiveCocoa也能很好的解決。RACCommand就包含這個概念,它有一個enabled信號,能讓你把按鈕的enabled屬性和信號綁定起來。你也許想試試這個類。
1.1.6.5 總結(jié)
????????希望本教程為你今后在自己的應(yīng)用中使用ReactiveCocoa打下了一個好的基礎(chǔ)。你可能需要一些練習(xí)來熟悉這些概念,但就像是語言或者編程,一旦你夯實(shí)基礎(chǔ),用起來也就很簡單了。ReactiveCocoa的核心就是信號,而它不過就是事件流。還能再更簡單點(diǎn)嗎?
????????在使用ReactiveCocoa后,我發(fā)現(xiàn)了一個有趣的事情,那就是你可以用很多種不同的方法來解決同一個問題。你可以用教程中的例子試試,調(diào)整一下信號,改改信號的分割和聚合。
????????ReactiveCocoa的主旨是讓你的代碼更簡潔易懂,這值得多想想。我個人認(rèn)為,如果邏輯可以用清晰的管道、流式語法來表示,那就很好理解這個應(yīng)用到底干了什么了。
????????在本系列教程的第二部分,你將會學(xué)到諸如錯誤處理、在不同線程中執(zhí)行代碼等高級用法。
1.2 使用技巧
1.2.1 與界面控件綁定
1.2.1.1 簡單屬性綁定
- (void) viewDidLoad
{
??? [super viewDidLoad];
??? //Create the View Model
??? self.viewModel = [CDWPlayerViewModel new];
??? //using with @strongify(self) this makes sure that self isn't retained in the blocks
??? //this is declared in RACEXTScope.h
??? @weakify(self);
??? //Start Binding our properties
??? RAC(self.nameField,text) = [RACObserve(self.viewModel, playerName) distinctUntilChanged];
??? [[self.nameField.rac_textSignal distinctUntilChanged] subscribeNext: ^(NSString*x) {
????? ????//this creates a reference to self that when used with @weakify(self);
????? ????//makes sure self isn't retained
????? ????@strongify(self);
????? ????self.viewModel.playerName= x;
??? }];
??? //the score property is a double, RC gives us updates as NSNumber which we just call
??? //stringValue on and bind that to the score field text
??? RAC(self.scoreField, text) = [RACObserve(self.viewModel, points) map:^id(NSNumber*value) {
????? ????return [value stringValue];
??? }];
??? //Setup bind the steppers values
??? self.scoreStepper.value = self.viewModel.points;
??? RAC(self.scoreStepper, stepValue) = RACObserve(self.viewModel, stepAmount);
??? RAC(self.scoreStepper, maximumValue) = RACObserve(self.viewModel, maxPoints);
??? RAC(self.scoreStepper, minimumValue) = RACObserve(self.viewModel, minPoints);
??? //bind the hidden field to a signal keeping track if
??? //we've updated less than a certain number times as the view model specifies
??? RAC(self.scoreStepper, hidden) = [RACObserve(self, scoreUpdates) map: ^id(NSNumber *x) {
????? ????@strongify(self);
????? ????return @(x.intValue >= self.viewModel.maxPointUpdates);
??? }];
??? //only take the maxPointUpdates number of score updates
??? //skip 1 because we don't want the 1st value provided, only changes
??? [[[RACObserve(self.scoreStepper,value) skip: 1] take: self.viewModel.maxPointUpdates] subscribeNext: ^(id newPoints) {
????? ????@strongify(self);
????????? self.viewModel.points = [newPoints doubleValue];
????? ????self.scoreUpdates++;
??? }];
??? //this signal should only trigger if we have "bad words" in our name
??? [self.viewModel.forbiddenNameSignal subscribeNext: ^(NSString*name) {
????? ????@strongify(self);
????? ????UIAlertView *alert = [[UIAlertView alloc] initWithTitle: @"Forbidden Name!" message: [NSString stringWithFormat: @"The name %@ has been forbidden!",name] delegate: nil cancelButtonTitle:? @"Ok" otherButtonTitles: nil];
????? ????[alert show];
????? ????self.viewModel.playerName = @"";
??? }];
??? //let the upload(save) button only be enabled when the view model says its valid
??? RAC(self.uploadButton, enabled) = self.viewModel.modelIsValidSignal;
??? //set the control action for our button to be the ViewModels action method
??? [self.uploadButton addTarget: self.viewModel action: @selector(uploadData:) forControlEvents: UIControlEventTouchUpInside];
??? //we can subscribe to the same thing in multiple locations
??? //here we skip the first 4 signals and take only 1 update
??? //and then disable/hide certain UI elements as our app
??? //only allows 5 updates
??? [[[[self.uploadButton rac_signalForControlEvents: UIControlEventTouchUpInside] skip: (kMaxUploads - 1)] take: 1] subscribeNext: ^(idx) {
????? ????@strongify(self);
????? ????self.nameField.enabled = NO;
????? ????self.scoreStepper.hidden = YES;
????? ????self.uploadButton.hidden = YES;
??? }];
}
1.2.1.2 UItableView綁定
(good)Binding to a UITableView from a ReactiveCocoa ViewModel
http://www.tuicool.com/articles/bYfmEjn
http://stackoverflow.com/questions/23203436/reactivecocoa-mvvm-with-uitableview
ReactiveCocoa Tutorial – The DefinitiveIntroduction: Part 1/2
http://www.raywenderlich.com/62699/reactivecocoa-tutorial-pt1
ReactiveCocoa Tutorial – The DefinitiveIntroduction: Part 2/2
http://www.raywenderlich.com/62796/reactivecocoa-tutorial-pt2
1.2.2 RAC宏
1.2.2.1 簡介
·RAC(TARGET, [KEYPATH, [NIL_VALUE]])
RAC(self.outputLabel, text) = self.inputTextField.rac_textSignal;
RAC(self.outputLabel, text, @"收到nil時就顯示我") = self.inputTextField.rac_textSignal;
????????這個宏是最常用的,RAC()總是出現(xiàn)在等號左邊,等號右邊是一個RACSignal,表示的意義是將一個對象的一個屬性和一個signal綁定,signal每產(chǎn)生一個value(id類型),都會自動執(zhí)行:
[TARGET setValue: value ?: NIL_VALUE forKeyPath: KEYPATH];
????????數(shù)字值會升級為NSNumber*,當(dāng)setValue:forKeyPath時會自動降級成基本類型(int, float, BOOL等),所以RAC綁定一個基本類型的值是沒有問題的。
? ??????RAC 可以看作某個屬性的值與一些信號的聯(lián)動。
- RAC(TARGET, KEYPATH, NILVALUE) will bind the `KEYPATH` of `TARGET` to the given signal. If the signal ever sends a `nil` value, the property will be set to `NILVALUE` instead. `NILVALUE` may itself be `nil` for object properties, but an NSValue should be used for primitive properties, to avoid an exception if `nil` is sent (which might occur if an intermediate object is set to `nil`).
- RAC(TARGET, KEYPATH) is the same as the above, but `NILVALUE` defaults to `nil`.
?
1.2.2.2 示例一
- (void)bindDetail {
????@weakify(self);
? ? [RACObserve(_channelEntity, postCount) subscribeNext: ^(NSNumber*postCount) {
? ? ?????@strongify(self);
? ? ? ? ?[self getDetailLabel].text = [NSString stringWithFormat: @"今日發(fā)帖%ld個,總發(fā)帖數(shù)%ld個", [self.channelEntity.postTodayCount longValue], [self.channelEntity.postCount longValue]];
??? }];
}
RAC(self.submitButton.enabled) = [RACSignal combineLatest: @[self.usernameField.rac_textSignal,?self.passwordField.rac_textSignal] reduce: ^id(NSString? *userName, NSString *password) {
????return @(userName.length=6 && password.length=6);
}];
1.2.2.3 示例二
RACSignal *photoSignal = [FRPPhotoImporter importPhotos];
RACSignal *photosLoaded = [photoSignal catch: ^RACSignal *(NSError *error) {
???????NSLog(@"Couldn't fetch photos from 500px: %@", error);
???????return [RACSignal empty];
}];
//將photosArray的變化綁定到self.collectionView
RAC(self, photosArray) = photosLoaded;
[photosLoaded subscribeCompleted: ^{
??????? @strongify(self);
??????? [self.collectionView reloadData];
}];
1.2.2.4 示例三
RAC(self, photosArray) = [[[[FRPPhotoImporter importPhotos] doCompleted: ^{
?????@strongify(self);
? ? ?[self.collectionView reloadData];
?}] logError] catchTo: [RACSignal empty]];
1.2.3 RACObserve宏
????作用是觀察TARGET的KEYPATH屬性,相當(dāng)于KVO,產(chǎn)生一個RACSignal。
????最常用的使用是和RAC宏綁定屬性:
RAC(self.outputLabel, text) = RACObserve(self.model, name);
????????上面的代碼將label的輸出和model的name屬性綁定,實(shí)現(xiàn)聯(lián)動,name但凡有變化都會使得label輸出。
RACObserve 監(jiān)聽屬性的改變,使用block的KVO
示例一:
[RACObserve(self.textField, text) subscribeNext: ^(NSString *newName){
????NSLog(@"%@",newName);
}];
示例二:
RAC(self.imageView, image) = [[RACObserve(self, photoModel.thumbnailData) ignore: nil] map: ^(NSData*data) {
??????? return [UIImage imageWithData: data];
}];
1.2.4 @weakify(Obj)? @strongify(Obj)
·?@weakify(Obj) ?@strongify(Obj)
????????這對宏在?RACEXTScope.h中定義,RACFramework好像沒有默認(rèn)引入,需要單獨(dú)import。他們的作用主要是在block內(nèi)部管理對self的引用:
??? @weakify(self);//定義了一個__weak的self_weak_變量
???[RACObserve(self, name) subscribeNext: ^(NSString *name) {
? ? ? ?@strongify(self);//局域定義了一個__strong的self指針指向self_weak
? ?????self.outputLabel.text = name;
??? }];
????????這個宏其實(shí)就是一個啥都沒干的@autoreleasepool {}前面的那個@,為了顯眼罷了。這兩個宏一定成對出現(xiàn),先weak再strong
1.2.5 冷信號(Cold)和熱信號(Hot)
??? 上面提到過這兩個概念,冷信號默認(rèn)什么也不干,比如下面這段代碼
RACSignal?*signal?=?[RACSignal?createSignal: ^RACDisposable?*?(id?subscriber)?{
????NSLog(@"triggered");
????[subscriber?sendNext: @"foobar"];
????[subscriber?sendCompleted];
????return?nil;
}];
??? 我們創(chuàng)建了一個Signal,但因?yàn)闆]有被subscribe,所以什么也不會發(fā)生。加了下面這段代碼后,signal就處于Hot的狀態(tài)了,block里的代碼就會被執(zhí)行。
[signal?subscribeCompleted: ^{
????NSLog(@"subscription?%u",?subscriptions);
}];
????????或許你會問,那如果這時又有一個新的subscriber了,signal的block還會被執(zhí)行嗎?這就牽扯到了另一個概念:Side Effect
1.2.6 簡單信號創(chuàng)建實(shí)例
1.2.6.1 異步網(wǎng)絡(luò)請求信號創(chuàng)建
+ (RACSignal*) rac_sendAsynchronousRequest: (NSURLRequest*)request {
???? NSCParameterAssert(request != nil);
???? return [[RACSignal createSignal: ^ RACDisposable * (id subscriber) {
???????????? NSOperationQueue *queue = [[NSOperationQueue alloc] init];
???????????? queue.name = @"com.github.ReactiveCocoa.NSURLConnectionRACSupport";
???????????? [NSURLConnection sendAsynchronousRequest: request queue: queue completionHandler: ^(NSURLResponse *response, NSData *data, NSError*error) {
? ? ? ? ? ? ? ?????if (data == nil) {
? ? ? ? ? ? ? ? ? ?????[subscriber sendError: error];
???????????????? ????}else{
???????????????????? ????[subscriber sendNext: RACTuplePack(response, data)];
???????????????????? ????[subscriber sendCompleted];
???????????????? ????}
???????????? }];
???????????? return [RACDisposable disposableWithBlock: ^{
???????????????? queue.suspended = YES;
???????????????? [queue cancelAllOperations];
???????????? }];
??????? }] setNameWithFormat: @"+rac_sendAsynchronousRequest: %@", request];
}
1.2.6.2 異步請求返回結(jié)果信號傳遞示例
+ (RACSignal*) requestPhotoData{
??? NSURLRequest *request = [self popularURLRequest];
??? return [[NSURLConnection rac_sendAsynchronousRequest: request] reduceEach: ^id(NSURLResponse *response, NSData *data){
??????? return data;
??? }];
}
1.2.6.3 Image異步下載信號創(chuàng)建
// creates a signal that fetches an image in the background, delivering
// it on the UI thread. This signal 'cancels' itself if the cell is re-used before the
// image is downloaded.
- (RACSignal *) signalForImage: (NSURL*)imageUrl {
? ? RACScheduler *scheduler = [RACScheduler schedulerWithPriority: RACSchedulerPriorityBackground];
?????RACSignal *imageDownloadSignal = [[RACSignal createSignal: ^RACDisposable *(id subscriber) {
?????????NSData *data = [NSData dataWithContentsOfURL: imageUrl];
?????????UIImage *image = [UIImage imageWithData: data];
?????????[subscriber sendNext: image];
?????????[subscriber sendCompleted];
?????????return nil;
????}] subscribeOn: scheduler];
?????return [[imageDownloadSignal takeUntil: self.rac_prepareForReuseSignal] deliverOn:[RACScheduler mainThreadScheduler]];
}
1.2.7 高級信號創(chuàng)建方法示例
@implementationFRPPhotoImporter
+ (NSURLRequest*) popularURLRequest {
????return [[PXRequest apiHelper] urlRequestForPhotoFeature: PXAPIHelperPhotoFeaturePopular resultsPerPage: 100 page: 0 photoSizes: PXPhotoModelSizeThumbnail sortOrder: PXAPIHelperSortOrderRating except: PXPhotoModelCategoryNude];
}
+ (NSURLRequest*) photoURLRequest: (FRPPhotoModel*)photoModel {
??? return [[PXRequest apiHelper] urlRequestForPhotoID: photoModel.identifier.integerValue];
}
+ (RACSignal*) requestPhotoData{
????NSURLRequest *request = [self popularURLRequest];
??? return [[NSURLConnection rac_sendAsynchronousRequest: request] reduceEach: ^id(NSURLResponse *response, NSData*data){
??????? return data;
??? }];
}
+ (RACSignal*) importPhotos {
??? return [[[[[self requestPhotoData] deliverOn: [RACScheduler mainThreadScheduler]] map: ^id(NSData *data) {
??????? id results = [NSJSONSerialization JSONObjectWithData: data options: 0 error: nil];
??????? return [[[results[@"photos"] rac_sequence] map: ^id(NSDictionary *photoDictionary) {
??????????? FRPPhotoModel *model = [FRPPhotoModel new];
??????????? [self configurePhotoModel: model withDictionary: photoDictionary];
??????????? [self downloadThumbnailForPhotoModel: model];
??????????? return model;
??????? }] array];
??? }] publish] autoconnect];
}
+ (RACSignal*) fetchPhotoDetails: (FRPPhotoModel*) photoModel {
??? NSURLRequest *request = [self photoURLRequest: photoModel];
??? return [[[[[[NSURLConnection rac_sendAsynchronousRequest: request] reduceEach: ^id(NSURLResponse *response, NSData *data){
??? ????return data;
??? }] deliverOn: [RACScheduler mainThreadScheduler]] map: ^id(NSData *data) {
??????? id results = [NSJSONSerialization JSONObjectWithData: data options:0 error: nil];
??????? [self configurePhotoModel: photoModel withDictionary: results];
??????? [self downloadFullsizedImageForPhotoModel: photoModel];
??????? return photoModel;
??? }] publish] autoconnect];
}
+ (RACSignal*) logInWithUsername: (NSString *)username password: (NSString*)password {
??? return [RACSignal createSignal: ^RACDisposable *(id subscriber) {
??????? [PXRequest authenticateWithUserName: username password: password completion: ^(BOOLsuccess) {
??????????? if(success) {
??????????????? [subscriber sendCompleted];
??????????? }else{
??????????????? [subscriber sendError: [NSError errorWithDomain: @"500px API" code: 0 userInfo: @{NSLocalizedDescriptionKey: @"Could not log in."}]];
??????????? }
??????? }];
??????? // Cannot cancel request
??????? return nil;
??? }];
}
+ (RACSignal*) voteForPhoto: (FRPPhotoModel*) photoModel {
??? return [[[RACSignal createSignal: ^RACDisposable *(id subscriber) {
??????? PXRequest *voteRequest = [PXRequest requestToVoteForPhoto: [photoModel.identifier integerValue] completion: ^(NSDictionary *results, NSError *error) {
??????????? if(error) {
??????????????? [subscriber sendError: error];
??????????? }else{
??????????????? photoModel.votedFor = YES;
??????????????? [subscriber sendCompleted];
??????????? }
??????? }];
??????? return [RACDisposable disposableWithBlock: ^{
??????????? if (voteRequest.requestStatus == PXRequestStatusStarted) {
??????????????? [voteRequest cancel];
??????????? }
??????? }];
??? }] publish] autoconnect];
}
#pragma mark - Private Methods
+(void) configurePhotoModel: (FRPPhotoModel *)photoModel withDictionary: (NSDictionary*) dictionary {
??? // Basics details fetched with the first, basic request
??? photoModel.photoName = dictionary[@"name"];
??? photoModel.identifier = dictionary[@"id"];
??? photoModel.photographerName = dictionary[@"user"][@"username"];
??? photoModel.rating = dictionary[@"rating"];
??? photoModel.thumbnailURL = [self urlForImageSize: 3 inDictionary: dictionary[@"images"]];
??? if (dictionary[@"voted"]) {
??????? photoModel.votedFor = [dictionary[@"voted"] boolValue];
??? }
??? // Extended attributes fetched with subsequent request
??? if (dictionary[@"comments_count"]) {
??????? photoModel.fullsizedURL = [self urlForImageSize: 4 inDictionary: dictionary[@"images"]];
??? }
}
+ (NSString*) urlForImageSize: (NSInteger)size inDictionary: (NSArray*) array {
??? /*
??? (
??????? {
??????????? size = 3;
??????????? url = "http://ppcdn.500px.org/49204370/b125a49d0863e0ba05d8196072b055876159f33e/3.jpg";
??????? }
???? );
???? */
??? return [[[[[array rac_sequence] filter: ^BOOL(NSDictionary *value) {
??????? return [value[@"size"] integerValue] == size;
??? }] map: ^id(id value) {
??????? return value[@"url"];
??? }] array] firstObject];
}
+ (void) downloadThumbnailForPhotoModel: (FRPPhotoModel*) photoModel {
??? RAC(photoModel, thumbnailData) = [self download: photoModel.thumbnailURL];
}
+(void) downloadFullsizedImageForPhotoModel: (FRPPhotoModel*) photoModel {
??? RAC(photoModel, fullsizedData) = [self download: photoModel.fullsizedURL];
}
+(RACSignal*) download: (NSString*) urlString {
??? NSAssert(urlString, @"URL must not be nil");
??? NSURLRequest *request = [NSURLRequest requestWithURL: [NSURL URLWithString: urlString]];
??? return [[[NSURLConnection rac_sendAsynchronousRequest: request] reduceEach: ^id(NSURLResponse *response, NSData *data){
??????? return data;
??? }] deliverOn: [RACScheduler mainThreadScheduler]];
}
@end
1.3 常見使用問題
1.3.1 編譯后報錯_OBJC_CLASS_$_RVMViewModel
解決方法:
????????看錯誤提示:你的項(xiàng)目設(shè)置里 other linker flags 選項(xiàng)覆蓋了pod定義的設(shè)置,導(dǎo)致了問題的存在。解決辦法:
????????在other linker flags里添加一行 $(inherited).
升級AFNetworking 從2.4到2.5后編譯報錯
http://www.cocoachina.com/bbs/read.php?tid-279299.html
1.3.2 屬性值綁定后無法監(jiān)聽到后續(xù)修改
????????屬性值的改動監(jiān)聽其實(shí)是基于KVO的,屬性值改動時,一定要以self.***的形式賦值才能觸發(fā)信號,如果僅以內(nèi)部成員的形式來賦值,則無法觸發(fā)信號。
WS(weakSelf);
??? _isLoadingVMArray = YES;
??? [HJUtilityInstance reloadEntityArrayWithCompleteBlock:^(HJResultData*reData){
??????? _isLoadingVMArray = NO;(無法觸發(fā)信號)
??????? weakSelf.isLoadingVMArray = NO;(可以觸發(fā)信號)
??? }];
1.3.3 重復(fù)綁定處理Signalis already bound to key path
(Good)http://stackoverflow.com/questions/22869109/signal-is-already-bound-to-key-path
The problem was that I did not call [subscriber setCompleted] to terminate the subscription.
解決重復(fù)綁定方法:
/**
?*? 加載頻道圖片
?*/
- (void) bindIcon{
??? WS(weakSelf);
? ? RACSignal*signal = RACObserve(_channelInfoVM, channelIconString);
??? [signal subscribeNext: ^(NSString*imgUrl) {
??????? [[weakSelf getIconImageView] sd_setImageWithURL: [NSURL URLWithString: imgUrl] placeholderImage: [UIImage imageNamed: @"ChannelDefaultImage"]];
??? }];
??? [signal takeUntil: [self rac_signalForSelector: @selector(bindIcon)]];
}
2 最佳實(shí)踐
2.1 設(shè)計原則
An Introduction to ReactiveCocoa: A BigNerd Ranch Tech Talk
ReactiveCocoa入門教程:第一部分
http://www.cocoachina.com/ios/20150123/10994.html
Reactive Cocoa Tutorial [0] = "Overview"
http://www.cnblogs.com/sunnyxx/p/3543542.html
Reactive Cocoa Tutorial [1] = "神奇的Macros"
http://www.cnblogs.com/sunnyxx/p/3544703.html
Reactive Cocoa Tutorial [2] = "百變RACStream"
http://www.cnblogs.com/sunnyxx/p/3547754.html
Reactive Cocoa Tutorial [3] = "RACSignal的巧克力工廠
http://www.cnblogs.com/sunnyxx/p/3547763.html
Reactive Cocoa Tutorial [4] =只取所需的Filters
http://www.cnblogs.com/sunnyxx/p/3676689.html
2.2 Demo示例工程說明
ReactiveCocoaDemo-master
????????此工程不錯,里面有如何綁定TableViewCell的使用說明
2.3 MVVM理解
2.3.1 參考鏈接
Model-View-ViewModel for iOS
http://www.teehanlax.com/blog/model-view-viewmodel-for-ios/
(Good)用Model-View-ViewModel構(gòu)建iOSApp
http://www.cocoachina.com/ios/20140716/9152.html
KVOController
https://github.com/facebook/KVOController
(Good)MVVMwithout ReactiveCocoa
http://www.cocoachina.com/ios/20151020/13795.html
一個MVVM架構(gòu)的iOS工程
https://github.com/lizelu/MVVM
淺談iOS中MVVM的架構(gòu)設(shè)計與團(tuán)隊(duì)協(xié)作
http://www.cocoachina.com/ios/20150122/10987.html
MVVM之解
http://bbs.csdn.net/topics/390696674
第9講:MVVM架構(gòu)
http://www.cnblogs.com/cdts_change/archive/2010/11/28/1890584.html
MVVM
3 參考鏈接
RAC中文資源列表
https://github.com/ReactiveCocoaChina/ReactiveCocoaChineseResources
春節(jié)研究ReactiveCocoa,寫了一個面向初學(xué)者的入門介紹:
http://www.cocoachina.com/bbs/read.php?tid=183897
Model-View-ViewModel for iOS
http://www.teehanlax.com/blog/model-view-viewmodel-for-ios/
Model-View-ViewModel for iOS [譯]
http://www.cnblogs.com/brycezhang/p/3840567.html
(good)ReactiveCocoa2實(shí)戰(zhàn)
http://www.cocoachina.com/industry/20140609/8737.html
https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/Documentation/BasicOperators.md
(Good)ReactiveCocoa
http://ju.outofmemory.cn/entry/103472
IOS響應(yīng)式編程框架ReactiveCocoa(RAC)使用示例
http://blog.csdn.net/dfqin/article/details/39164241
ReactiveCocoa函數(shù)式響應(yīng)編程簡介一
http://cocoadocs.org/docsets/ReactiveCocoa/2.3.1/Classes/RACTuple.html
使用ReactiveCocoa實(shí)現(xiàn)iOS平臺響應(yīng)式編程
http://blog.csdn.net/xdrt81y/article/details/30624469
ReactiveCocoa與Functional Reactive Programming
http://limboy.me/ios/2013/06/19/frp-reactivecocoa.html
說說ReactiveCocoa2
http://limboy.me/ios/2013/12/27/reactivecocoa-2.html
基于AFNetworking2.0和ReactiveCocoa2.1的iOSREST Client
http://limboy.me/ios/2014/01/05/ios-rest-client-implementation.html
An Introduction to ReactiveCocoa: A BigNerd Ranch Tech Talk
Remove a ReactiveCocoa signal from a control
http://stackoverflow.com/questions/19650802/remove-a-reactivecocoa-signal-from-a-control
RAC and cell reuse: putting deliverOn: in the right place?
http://stackoverflow.com/questions/27172874/rac-and-cell-reuse-putting-deliveron-in-the-right-place
Reactive Cocoa and multiple AFNetworking requests in shortperiod of time
ReactiveCocoa binding “networkActivityIndicator” Crushes
http://stackoverflow.com/questions/25124722/reactivecocoa-binding-networkactivityindicator-crushes
http://stackoverflow.com/search?q=signal+is+already+bound+to+key+path
Replacing the Objective-C “Delegate Pattern” withReactiveCocoa
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
ashfurrow/C-41