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個(gè)字符長(zhǎng)度的用戶名,那么你可以使用filter操作來實(shí)現(xiàn)這個(gè)目的。把之前加在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只能怪輸入幾個(gè)字,你會(huì)發(fā)現(xiàn)只有當(dāng)輸入超過3個(gè)字符時(shí)才會(huì)有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)建的只是一個(gè)很簡(jiǎn)單的管道。這就是響應(yīng)式編程的本質(zhì),根據(jù)數(shù)據(jù)流來表達(dá)應(yīng)用的功能嵌屎。用圖形來表達(dá)就是下面這樣的:
????????從上面的圖中可以看到,rac_textSignal是起始事件恍涂。然后數(shù)據(jù)通過一個(gè)filter宝惰,如果這個(gè)事件包含一個(gè)長(zhǎng)度超過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的每個(gè)操作都會(huì)返回一個(gè)RACsignal什燕,這在術(shù)語上叫做連貫接口(fluent interface)粘勒。這個(gè)功能可以讓你直接構(gòu)建管道,而不用每一步都使用本地變量屎即。
????????注意:ReactiveCocoa大量使用block庙睡。如果你是block新手,你可能想看看Apple官方的block編程指南技俐。如果你熟悉block乘陪,但是覺得block的語法有些奇怪和難記,你可能會(huì)想看看這個(gè)有趣又實(shí)用的網(wǎng)頁f*****gblocksyntax.com雕擂。
1.1.2 類型轉(zhuǎn)換filter(數(shù)據(jù)過濾)
????????如果你之前把代碼分成了多個(gè)步驟啡邑,現(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的值肯定是個(gè)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)瓷马。有意思的是(?)跨晴,事件可以包括任何事情欧聘。下面來展示一下,在管道中添加另一個(gè)操作端盆。把添加在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)行怀骤,你會(huì)發(fā)現(xiàn)log輸出變成了文本的長(zhǎng)度而不是內(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從上一個(gè)next事件接收數(shù)據(jù)蒋伦,通過執(zhí)行block把返回值傳給下一個(gè)next事件。在上面的代碼中焚鹊,map以NSString為輸入痕届,取字符串的長(zhǎng)度,返回一個(gè)NSNumber末患。
????????來看下面的圖片:
????????能看到map操作之后的步驟收到的都是NSNumber實(shí)例研叫。你可以使用map操作來把接收的數(shù)據(jù)轉(zhuǎn)換成想要的類型,只要它是個(gè)對(duì)象璧针。
????????注意:在上面的例子中text.length返回一個(gè)NSUInteger嚷炉,是一個(gè)基本類型。為了將它作為事件的內(nèi)容探橱,NSUInteger必須被封裝申屹。幸運(yùn)的是Objective-C literal syntax提供了一種簡(jiǎn)單的方法來封裝——@ (text.length)。
????????現(xiàn)在差不多是時(shí)候用所學(xué)的內(nèi)容來更新一下ReactivePlayground應(yīng)用了隧膏。你可以把之前的添加代碼都刪除了哗讥。
1.1.4 創(chuàng)建有效狀態(tài)信號(hào)RACSignal
????????首先要做的就是創(chuàng)建一些信號(hào),來表示用戶名和密碼輸入框中的輸入內(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]);
}];
????????可以看到杆煞,上面的代碼對(duì)每個(gè)輸入框的rac_textSignal應(yīng)用了一個(gè)map轉(zhuǎn)換。輸出是一個(gè)用NSNumber封裝的布爾值。
????????下一步是轉(zhuǎn)換這些信號(hào)索绪,從而能為輸入框設(shè)置不同的背景顏色∑肚模基本上就是瑞驱,你訂閱這些信號(hào),然后用接收到的值來更新輸入框的背景顏色窄坦。下面有一種方法:
[[validPasswordSignal map: ^id(NSNumber?*passwordValid){
????return?[passwordValid?boolValue]???[UIColor?clearColor]:[UIColor?yellowColor];
}] subscribeNext: ^(UIColor?*color){
????self.passwordTextField.backgroundColor?=?color;
}];
????????(不要使用這段代碼唤反,下面有一種更好的寫法!)
????????從概念上來說鸭津,就是把之前信號(hào)的輸出應(yīng)用到輸入框的backgroundColor屬性上彤侍。但是上面的用法不是很好。
????????幸運(yùn)的是逆趋,ReactiveCocoa提供了一個(gè)宏來更好的完成上面的事情盏阶。把下面的代碼直接加到viewDidLoad中兩個(gè)信號(hào)的代碼后面:
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宏允許直接把信號(hào)的輸出應(yīng)用到對(duì)象的屬性上。RAC宏有兩個(gè)參數(shù)闻书,第一個(gè)是需要設(shè)置屬性值的對(duì)象名斟,第二個(gè)是屬性名。每次信號(hào)產(chǎn)生一個(gè)next事件魄眉,傳遞過來的值都會(huì)應(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)容無效時(shí),輸入框看起來高亮了宫屠,有效時(shí)又透明了池摧。
????????現(xiàn)在的邏輯用圖形來表示就是下面這樣的。能看到有兩條簡(jiǎn)單的管道激况,兩個(gè)文本信號(hào)作彤,經(jīng)過一個(gè)map轉(zhuǎn)為表示是否有效的布爾值,再經(jīng)過一個(gè)map轉(zhuǎn)為UIColor乌逐,而這個(gè)UIColor已經(jīng)和輸入框的背景顏色綁定了竭讳。
????????你是否好奇為什么要?jiǎng)?chuàng)建兩個(gè)分開的validPasswordSignal和validUsernameSignal呢,而不是每個(gè)輸入框一個(gè)單獨(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 聚合信號(hào)combineLatest
????????目前在應(yīng)用中胰舆,登錄按鈕只有當(dāng)用戶名和密碼輸入框的輸入都有效時(shí)才工作∩叮現(xiàn)在要把這里改成響應(yīng)式的。
????????現(xiàn)在的代碼中已經(jīng)有可以產(chǎn)生用戶名和密碼輸入框是否有效的信號(hào)了——validUsernameSignal和validPasswordSignal了「苛現(xiàn)在需要做的就是聚合這兩個(gè)信號(hào)來決定登錄按鈕是否可用棘幸。
????????把下面的代碼添加到viewDidLoad的末尾:
RACSignal?*signUpActiveSignal?= [RACSignalcombineLatest: @[validUsernameSignal,?validPasswordSignal] reduce: ^id(NSNumber *usernameValid,?NSNumber?*passwordValid){
? ??return?@([usernameValid?boolValue]&&[passwordValid?boolValue]);
}];
????????上面的代碼使用combineLatest:reduce:方法把validUsernameSignal和validPasswordSignal產(chǎn)生的最新的值聚合在一起,并生成一個(gè)新的信號(hào)倦零。每次這兩個(gè)源信號(hào)的任何一個(gè)產(chǎn)生新值時(shí)误续,reduce
block都會(huì)執(zhí)行,block的返回值會(huì)發(fā)給下一個(gè)信號(hào)扫茅。
????????注意:RACsignal的這個(gè)方法可以聚合任意數(shù)量的信號(hào)蹋嵌,reduce block的參數(shù)和每個(gè)源信號(hào)相關(guān)。ReactiveCocoa有一個(gè)工具類RACBlockTrampoline葫隙,它在內(nèi)部處理reduce block的可變參數(shù)栽烂。實(shí)際上在ReactiveCocoa的實(shí)現(xiàn)中有很多隱藏的技巧,值得你去看看恋脚。
????????現(xiàn)在已經(jīng)有了合適的信號(hào)愕鼓,把下面的代碼添加到viewDidLoad的末尾。這會(huì)把信號(hào)和按鈕的enabled屬性綁定慧起。
[signUpActiveSignal?subscribeNext: ^(NSNumber *signupActive){
????self.signInButton.enabled?= [signupActive?boolValue];
}];
????????在運(yùn)行之前菇晃,把以前的舊實(shí)現(xiàn)刪掉。把下面這兩個(gè)屬性刪掉蚓挤。
@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)用戶名和密碼輸入有效時(shí)缤剧,按鈕就是可用的馅袁,和以前一樣。
????????現(xiàn)在應(yīng)用的邏輯就是下面這樣的:
????????上圖展示了一些重要的概念荒辕,你可以使用ReactiveCocoa來完成一些重量級(jí)的任務(wù)汗销。
? 分割——信號(hào)可以有很多subscriber,也就是作為很多后續(xù)步驟的源抵窒。注意上圖中那個(gè)用來表示用戶名和密碼有效性的布爾信號(hào)弛针,它被分割成多個(gè),用于不同的地方李皇。
? 聚合——多個(gè)信號(hào)可以聚合成一個(gè)新的信號(hào)削茁,在上面的例子中,兩個(gè)布爾信號(hào)聚合成了一個(gè)。實(shí)際上你可以聚合并產(chǎn)生任何類型的信號(hào)茧跋。
????????這些改動(dòng)的結(jié)果就是慰丛,代碼中沒有用來表示兩個(gè)輸入框有效狀態(tài)的私有屬性了。這就是用響應(yīng)式編程的一個(gè)關(guān)鍵區(qū)別瘾杭,你不需要使用實(shí)例變量來追蹤瞬時(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方法是綁定的页徐。下面會(huì)用響應(yīng)的方法替換,所以首先要做的就是斷開當(dāng)前的storyboard action银萍。
????????打開Main.storyboard变勇,找到登錄按鈕,按住ctrl鍵單擊贴唇,打開outlet/action連接框搀绣,然后點(diǎn)擊x來斷開連接。如果你找不到的話戳气,下圖中紅色箭頭指示的就是刪除按鈕链患。
????????你已經(jīng)知道了ReactiveCocoa框架是如何給基本UIKit控件添加屬性和方法的了。目前你已經(jīng)使用了rac_textSignal瓶您,它會(huì)在文本發(fā)生變化時(shí)產(chǎn)生信號(hào)麻捻。為了處理按鈕的事件,現(xiàn)在需要用到ReactiveCocoa為UIKit添加的另一個(gè)方法呀袱,rac_signalForControlEvents贸毕。
????????現(xiàn)在回到RWViewController.m,把下面的代碼添加到viewDidLoad的末尾:
[[self.signInButton rac_signalForControlEvents: UIControlEventTouchUpInside] subscribeNext: ^(id?x)?{
?????NSLog(@"button?clicked");
}];
????????上面的代碼從按鈕的UIControlEventTouchUpInside事件創(chuàng)建了一個(gè)信號(hào)夜赵,然后添加了一個(gè)訂閱明棍,在每次事件發(fā)生時(shí)都會(huì)輸出log。
????????編譯運(yùn)行寇僧,確保的確有l(wèi)og輸出摊腋。按鈕只在用戶名和密碼框輸入有效時(shí)可用,所以在點(diǎn)擊按鈕前需要在兩個(gè)文本框中輸入一些內(nèi)容嘁傀。
????????可以看到Xcode控制臺(tái)的輸出和下面的類似:
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)擊事件的信號(hào)歌豺,下一步就是把它和登錄流程連接起來。那么問題就來了心包,打開RWDummySignInService.h类咧,看一下接口:
typedef?void?(^RWSignInResponse)(BOOL);
@interface?RWDummySignInService?:?NSObject
-?(void) signInWithUsername: (NSString?*)username password: (NSString?*)password complete: (RWSignInResponse)completeBlock;
@end
????????這個(gè)service有3個(gè)參數(shù),用戶名、密碼和一個(gè)完成回調(diào)block痕惋。這個(gè)block會(huì)在登錄成功或失敗時(shí)執(zhí)行区宇。你可以在按鈕點(diǎn)擊事件的subscribeNext: blcok里直接調(diào)用這個(gè)方法,但是為什么你要這么做值戳?
????????注意:本教程為了簡(jiǎn)便使用了一個(gè)假的service议谷,所以它不依賴任何外部API。但你現(xiàn)在的確遇到了一個(gè)問題堕虹,如何使用這些不是用信號(hào)表示的API呢卧晓?
1.1.6.2 創(chuàng)建信號(hào)
????????幸運(yùn)的是,把已有的異步API用信號(hào)的方式來表示相當(dāng)簡(jiǎn)單赴捞。首先把RWViewController.m中的signInButtonTouched:刪掉逼裆。你會(huì)用響應(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)建了一個(gè)信號(hào)胜宇,使用用戶名和密碼登錄。現(xiàn)在分解來看一下恢着。
????????上面的代碼使用RACSignal的createSignal:方法來創(chuàng)建信號(hào)桐愉。方法的入?yún)⑹且粋€(gè)block,這個(gè)block描述了這個(gè)信號(hào)掰派。當(dāng)這個(gè)信號(hào)有subscriber時(shí)从诲,block里的代碼就會(huì)執(zhí)行。????????
????????block的入?yún)⑹且粋€(gè)subscriber實(shí)例靡羡,它遵循RACSubscriber協(xié)議系洛,協(xié)議里有一些方法來產(chǎn)生事件,你可以發(fā)送任意數(shù)量的next事件亿眠,或者用error\complete事件來終止碎罚。本例中,信號(hào)發(fā)送了一個(gè)next事件來表示登錄是否成功纳像,隨后是一個(gè)complete事件荆烈。
????????這個(gè)block的返回值是一個(gè)RACDisposable對(duì)象,它允許你在一個(gè)訂閱被取消時(shí)執(zhí)行一些清理工作竟趾。當(dāng)前的信號(hào)不需要執(zhí)行清理操作憔购,所以返回nil就可以了。
????????可以看到岔帽,把一個(gè)異步API用信號(hào)封裝是多簡(jiǎn)單玫鸟!
????????現(xiàn)在就來使用這個(gè)新的信號(hào)。把之前添加在viewDidLoad中的代碼更新成下面這樣的:
[[[self.signInButton rac_signalForControlEvents: UIControlEventTouchUpInside] map: ^id(id?x){
?????return?[self?signInSignal];
}] subscribeNext: ^(id?x){
?????NSLog(@"Sign?in?result:?%@",?x);
}];
????????上面的代碼使用map方法犀勒,把按鈕點(diǎn)擊信號(hào)轉(zhuǎn)換成了登錄信號(hào)屎飘。subscriber輸出log妥曲。
????????編譯運(yùn)行,點(diǎn)擊登錄按鈕钦购,查看Xcode的控制臺(tái)檐盟,等等,輸出的這是個(gè)什么鬼押桃?
2014-01-08?21:00:25.919?RWReactivePlayground[33818:a0b]?Sign?inresult:
name:?+createSignal:
????????沒錯(cuò)葵萎,你已經(jīng)給subscribeNext:的block傳入了一個(gè)信號(hào),但傳入的不是登錄結(jié)果的信號(hào)唱凯。
????????下圖展示了到底發(fā)生了什么:
????????當(dāng)點(diǎn)擊按鈕時(shí)羡忘,rac_signalForControlEvents發(fā)送了一個(gè)next事件(事件的data是UIButton)。map操作創(chuàng)建并返回了登錄信號(hào)磕昼,這意味著后續(xù)步驟都會(huì)收到一個(gè)RACSignal卷雕。這就是你在subscribeNext:這步看到的。
????????上面問題的解決方法掰烟,有時(shí)候叫做信號(hào)中的信號(hào)爽蝴,換句話說就是一個(gè)外部信號(hào)里面還有一個(gè)內(nèi)部信號(hào)沐批。你可以在外部信號(hào)的subscribeNext:block里訂閱內(nèi)部信號(hào)纫骑。不過這樣嵌套太混亂啦,還好ReactiveCocoa已經(jīng)解決了這個(gè)問題九孩。
1.1.6.3 信號(hào)中的信號(hào)flattenMap
????????解決的方法很簡(jiǎn)單先馆,只需要把map操作改成flattenMap就可以了:
[[[self.signInButton rac_signalForControlEvents: UIControlEventTouchUpInside] flattenMap: ^id(id?x){
?????return?[self?signInSignal];
}] subscribeNext: ^(id?x){
?????NSLog(@"Sign?in?result:?%@",?x);
}];
????????這個(gè)操作把按鈕點(diǎn)擊事件轉(zhuǎn)換為登錄信號(hào),同時(shí)還從內(nèi)部信號(hào)發(fā)送事件到外部信號(hào)躺彬。
????????編譯運(yùn)行煤墙,注意控制臺(tái),現(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
????????還不錯(cuò)仿野。
????????現(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從登錄信號(hào)中取得結(jié)果脚作,相應(yīng)地更新signInFailureText是否可見。如果登錄成功執(zhí)行導(dǎo)航跳轉(zhuǎn)缔刹。
????????編譯運(yùn)行球涛,應(yīng)該就能再看到可愛的小貓啦!喵~
????????你注意到這個(gè)應(yīng)用現(xiàn)在有一些用戶體驗(yàn)上的小問題了嗎校镐?當(dāng)?shù)卿泂ervice正在校驗(yàn)用戶名和密碼時(shí)亿扁,登錄按鈕應(yīng)該是不可點(diǎn)擊的。這會(huì)防止用戶多次執(zhí)行登錄操作鸟廓。還有从祝,如果登錄失敗了襟己,用戶再次嘗試登錄時(shí),應(yīng)該隱藏錯(cuò)誤信息牍陌。
????????這個(gè)邏輯應(yīng)該怎么添加呢稀蟋?改變按鈕的可用狀態(tài)并不是轉(zhuǎn)換(map)、過濾(filter)或者其他已經(jīng)學(xué)過的概念呐赡。其實(shí)這個(gè)就叫做“副作用”退客,換句話說就是在一個(gè)next事件發(fā)生時(shí)執(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ù)期的一樣。現(xiàn)在所有的工作都已經(jīng)完成了驻龟,這個(gè)應(yīng)用已經(jīng)是響應(yīng)式的啦忆首。你中途哪里出了問題,可以下載最終的工程(依賴庫都有)挟伙,或者在Github上找到這份代碼,教程中的每一次編譯運(yùn)行都有對(duì)應(yīng)的commit模孩。
????????注意:在異步操作執(zhí)行的過程中禁用按鈕是一個(gè)常見的問題尖阔,ReactiveCocoa也能很好的解決。RACCommand就包含這個(gè)概念榨咐,它有一個(gè)enabled信號(hào)介却,能讓你把按鈕的enabled屬性和信號(hào)綁定起來。你也許想試試這個(gè)類块茁。
1.1.6.5 總結(jié)
????????希望本教程為你今后在自己的應(yīng)用中使用ReactiveCocoa打下了一個(gè)好的基礎(chǔ)齿坷。你可能需要一些練習(xí)來熟悉這些概念,但就像是語言或者編程龟劲,一旦你夯實(shí)基礎(chǔ)胃夏,用起來也就很簡(jiǎn)單了。ReactiveCocoa的核心就是信號(hào)昌跌,而它不過就是事件流仰禀。還能再更簡(jiǎn)單點(diǎn)嗎?
????????在使用ReactiveCocoa后蚕愤,我發(fā)現(xiàn)了一個(gè)有趣的事情答恶,那就是你可以用很多種不同的方法來解決同一個(gè)問題饺蚊。你可以用教程中的例子試試,調(diào)整一下信號(hào)悬嗓,改改信號(hào)的分割和聚合污呼。
????????ReactiveCocoa的主旨是讓你的代碼更簡(jiǎn)潔易懂,這值得多想想包竹。我個(gè)人認(rèn)為燕酷,如果邏輯可以用清晰的管道、流式語法來表示周瞎,那就很好理解這個(gè)應(yīng)用到底干了什么了苗缩。
????????在本系列教程的第二部分,你將會(huì)學(xué)到諸如錯(cuò)誤處理声诸、在不同線程中執(zhí)行代碼等高級(jí)用法酱讶。
1.2 使用技巧
1.2.1 與界面控件綁定
1.2.1.1 簡(jiǎn)單屬性綁定
- (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 簡(jiǎn)介
·RAC(TARGET, [KEYPATH, [NIL_VALUE]])
RAC(self.outputLabel, text) = self.inputTextField.rac_textSignal;
RAC(self.outputLabel, text, @"收到nil時(shí)就顯示我") = self.inputTextField.rac_textSignal;
????????這個(gè)宏是最常用的,RAC()總是出現(xiàn)在等號(hào)左邊彼乌,等號(hào)右邊是一個(gè)RACSignal泻肯,表示的意義是將一個(gè)對(duì)象的一個(gè)屬性和一個(gè)signal綁定,signal每產(chǎn)生一個(gè)value(id類型)慰照,都會(huì)自動(dòng)執(zhí)行:
[TARGET setValue: value ?: NIL_VALUE forKeyPath: KEYPATH];
????????數(shù)字值會(huì)升級(jí)為NSNumber*灶挟,當(dāng)setValue:forKeyPath時(shí)會(huì)自動(dòng)降級(jí)成基本類型(int, float, BOOL等),所以RAC綁定一個(gè)基本類型的值是沒有問題的焚挠。
? ??????RAC 可以看作某個(gè)屬性的值與一些信號(hào)的聯(lián)動(dòng)膏萧。
- 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個(gè)漓骚,總發(fā)帖數(shù)%ld個(gè)", [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)生一個(gè)RACSignal蝌蹂。
????最常用的使用是和RAC宏綁定屬性:
RAC(self.outputLabel, text) = RACObserve(self.model, name);
????????上面的代碼將label的輸出和model的name屬性綁定噩斟,實(shí)現(xiàn)聯(lián)動(dòng),name但凡有變化都會(huì)使得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)
????????這對(duì)宏在?RACEXTScope.h中定義,RACFramework好像沒有默認(rèn)引入齐鲤,需要單獨(dú)import斥废。他們的作用主要是在block內(nèi)部管理對(duì)self的引用:
??? @weakify(self);//定義了一個(gè)__weak的self_weak_變量
???[RACObserve(self, name) subscribeNext: ^(NSString *name) {
? ? ? ?@strongify(self);//局域定義了一個(gè)__strong的self指針指向self_weak
? ?????self.outputLabel.text = name;
??? }];
????????這個(gè)宏其實(shí)就是一個(gè)啥都沒干的@autoreleasepool {}前面的那個(gè)@,為了顯眼罷了给郊。這兩個(gè)宏一定成對(duì)出現(xiàn)牡肉,先weak再strong
1.2.5 冷信號(hào)(Cold)和熱信號(hào)(Hot)
??? 上面提到過這兩個(gè)概念,冷信號(hào)默認(rèn)什么也不干淆九,比如下面這段代碼
RACSignal?*signal?=?[RACSignal?createSignal: ^RACDisposable?*?(id?subscriber)?{
????NSLog(@"triggered");
????[subscriber?sendNext: @"foobar"];
????[subscriber?sendCompleted];
????return?nil;
}];
??? 我們創(chuàng)建了一個(gè)Signal统锤,但因?yàn)闆]有被subscribe毛俏,所以什么也不會(huì)發(fā)生。加了下面這段代碼后饲窿,signal就處于Hot的狀態(tài)了煌寇,block里的代碼就會(huì)被執(zhí)行。
[signal?subscribeCompleted: ^{
????NSLog(@"subscription?%u",?subscriptions);
}];
????????或許你會(huì)問逾雄,那如果這時(shí)又有一個(gè)新的subscriber了阀溶,signal的block還會(huì)被執(zhí)行嗎?這就牽扯到了另一個(gè)概念:Side Effect
1.2.6 簡(jiǎn)單信號(hào)創(chuàng)建實(shí)例
1.2.6.1 異步網(wǎng)絡(luò)請(qǐng)求信號(hào)創(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 異步請(qǐng)求返回結(jié)果信號(hào)傳遞示例
+ (RACSignal*) requestPhotoData{
??? NSURLRequest *request = [self popularURLRequest];
??? return [[NSURLConnection rac_sendAsynchronousRequest: request] reduceEach: ^id(NSURLResponse *response, NSData *data){
??????? return data;
??? }];
}
1.2.6.3 Image異步下載信號(hào)創(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 高級(jí)信號(hào)創(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 編譯后報(bào)錯(cuò)_OBJC_CLASS_$_RVMViewModel
解決方法:
????????看錯(cuò)誤提示:你的項(xiàng)目設(shè)置里 other linker flags 選項(xiàng)覆蓋了pod定義的設(shè)置鸦泳,導(dǎo)致了問題的存在淌哟。解決辦法:
????????在other linker flags里添加一行 $(inherited).
升級(jí)AFNetworking 從2.4到2.5后編譯報(bào)錯(cuò)
http://www.cocoachina.com/bbs/read.php?tid-279299.html
1.3.2 屬性值綁定后無法監(jiān)聽到后續(xù)修改
????????屬性值的改動(dòng)監(jiān)聽其實(shí)是基于KVO的,屬性值改動(dòng)時(shí)辽故,一定要以self.***的形式賦值才能觸發(fā)信號(hào)徒仓,如果僅以內(nèi)部成員的形式來賦值,則無法觸發(fā)信號(hào)誊垢。
WS(weakSelf);
??? _isLoadingVMArray = YES;
??? [HJUtilityInstance reloadEntityArrayWithCompleteBlock:^(HJResultData*reData){
??????? _isLoadingVMArray = NO;(無法觸發(fā)信號(hào))
??????? weakSelf.isLoadingVMArray = NO;(可以觸發(fā)信號(hào))
??? }];
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è)計(jì)原則
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
????????此工程不錯(cuò)掉弛,里面有如何綁定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
一個(gè)MVVM架構(gòu)的iOS工程
https://github.com/lizelu/MVVM
淺談iOS中MVVM的架構(gòu)設(shè)計(jì)與團(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,寫了一個(gè)面向初學(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)編程簡(jiǎn)介一
http://cocoadocs.org/docsets/ReactiveCocoa/2.3.1/Classes/RACTuple.html
使用ReactiveCocoa實(shí)現(xiàn)iOS平臺(tái)響應(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