【IOS開發(fā)高級(jí)系列】MVVM—ReactiveCocoa架構(gòu)設(shè)計(jì)專題(三)

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)聽屬性的改變剃允,使用blockKVO

示例一:

[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

https://vimeo.com/78749139

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

http://baike.baidu.com/link?url=8F5nFTnRJIZHbybQtR5ibq97nd-0aCgA89hRw9dHyPtWBGb_vfNdhrciM5-BiDs51hNQzYnq2gLVKuqEfX3_eK


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://wenku.baidu.com/link?url=RtwVmjAop1mxsxGcqHQFDgEMtYca2KXLR5SQlN8cl9RSDz1i0VmGY0YnarffFPSnVaT7aXb1rmAtNWvRsVc4nwP3TbJBfZtxMS3ZVugKT9e

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

https://vimeo.com/78749139

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

http://stackoverflow.com/questions/23059512/reactive-cocoa-and-multiple-afnetworking-requests-in-short-period-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://www.itiger.me/?p=38

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

https://leanpub.com/iosfrp

ashfurrow/C-41

https://github.com/AshFurrow/C-41

http://vimeo.com/65637501

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末喂走,一起剝皮案震驚了整個(gè)濱河市殃饿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌芋肠,老刑警劉巖乎芳,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異帖池,居然都是意外死亡奈惑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門睡汹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肴甸,“玉大人,你說我怎么就攤上這事囚巴≡冢” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵彤叉,是天一觀的道長(zhǎng)庶柿。 經(jīng)常有香客問我,道長(zhǎng)秽浇,這世上最難降的妖魔是什么浮庐? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮兼呵,結(jié)果婚禮上兔辅,老公的妹妹穿的比我還像新娘腊敲。我一直安慰自己,他們只是感情好维苔,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布碰辅。 她就那樣靜靜地躺著,像睡著了一般介时。 火紅的嫁衣襯著肌膚如雪没宾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天沸柔,我揣著相機(jī)與錄音循衰,去河邊找鬼。 笑死褐澎,一個(gè)胖子當(dāng)著我的面吹牛会钝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播工三,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼迁酸,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了俭正?” 一聲冷哼從身側(cè)響起奸鬓,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎掸读,沒想到半個(gè)月后串远,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡儿惫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年澡罚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姥闪。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡始苇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出筐喳,到底是詐尸還是另有隱情,我是刑警寧澤函喉,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布避归,位于F島的核電站,受9級(jí)特大地震影響管呵,放射性物質(zhì)發(fā)生泄漏梳毙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一捐下、第九天 我趴在偏房一處隱蔽的房頂上張望账锹。 院中可真熱鬧萌业,春花似錦、人聲如沸奸柬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽廓奕。三九已至抱婉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間桌粉,已是汗流浹背蒸绩。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铃肯,地道東北人患亿。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像押逼,于是被迫代替她去往敵國(guó)和親窍育。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容