亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

ReactiveCocoa入門教程——第一部分(轉)

2019-11-14 18:09:11
字體:
來源:轉載
供稿:網友

作為一個iOS開發者,你寫的每一行代碼幾乎都是在響應某個事件,例如按鈕的點擊,收到網絡消息,屬性的變化(通過KVO)或者用戶位置的變化(通過CoreLocation)。但是這些事件都用不同的方式來處理,比如action、delegate、KVO、callback等。ReactiveCocoa為事件定義了一個標準接口,從而可以使用一些基本工具來更容易的連接、過濾和組合。

如果你對上面說的還比較疑惑,那還是繼續往下看吧。

ReactiveCocoa結合了幾種編程風格:

函數式編程(Functional PRogramming):使用高階函數,例如函數用其他函數作為參數。

響應式編程(Reactive Programming):關注于數據流和變化傳播。

 所以,你可能聽說過ReactiveCocoa被描述為函數響應式編程(FRP)框架。

 這就是這篇教程要講的內容。編程范式是個不錯的主題,但是本篇教程的其余部分將會通過一個例子來實踐。 

Reactive Playground

通過這篇教程,一個簡單的范例應用Reactive Playground ,你將會了解到響應式編程。下載初始工程,然后編譯運行一下確保你已經把一切都設置正確了。

 Reactive Playground是一個非常簡單的應用,它為用戶展示了一個登錄頁。在用戶名框輸入user,在密碼框輸入passWord,然后你就能看到有一只可愛小貓咪的歡迎頁了。

  

呀,真是可愛啊。

 現在可以花一些時間來看一下初始工程的代碼。很簡單,用不了多少時間。

 打開RWViewController.m看一下。你多快能找到控制登錄按鈕是否可用的條件?判斷顯示/隱藏登錄失敗label的條件是什么?在這個相對簡單的例子里,可能只用一兩分鐘就能回答這些問題。但是對于更復雜的例子,這些所花的時間可能就比較多了。

 使用ReactiveCocoa,可以使應用的基本邏輯變得相當簡潔。是時候開始啦。

添加ReactiveCocoa框架

添加ReactiveCocoa框架最簡單的方法就是用CocoaPods。如果你從沒用過CocoaPods,那還是先去看看CocoaPods簡介這篇教程吧。請至少看完教程中初始化的步驟,這樣你才能安裝框架。

注意:如果不想用CocoaPods,你仍然可以使用ReactiveCocoa,具體查看Github文檔中引入ReactiveCocoa的步驟描述。

譯注:我就是不喜歡用CocoaPods的那波人。所以我首先使用了Github上提供的方法,但是在第二步執行bootstrap時提示缺少xctool,我就果斷放棄了,還是乖乖用CocoaPods吧。

 具體怎么使用CocoaPods安裝就不詳細講解了。 

開動

就像在介紹中提到的,RAC為應用中發生的不同事件流提供了一個標準接口。在ReactiveCocoa術語中這個叫做信號(signal),由RACSignal類表示。

 打開應用的初始view controller,RWViewController.m ,引入ReactiveCocoa的頭文件。

#import <ReactiveCocoa/ReactiveCocoa.h>

不要替換已有的代碼,將下面的代碼添加到viewDidLoad方法的最后:

[self.usernameTextField.rac_textSignal subscribeNext:^(id x){  NSLog(@"%@", x);}];

 編譯運行,在用戶名輸入框中輸幾個字。注意console的輸出應該和下面的類似。

2013-12-24 14:48:50.359 RWReactivePlayground[9193:a0b] i2013-12-24 14:48:50.436 RWReactivePlayground[9193:a0b] is2013-12-24 14:48:50.541 RWReactivePlayground[9193:a0b] is 2013-12-24 14:48:50.695 RWReactivePlayground[9193:a0b] is t2013-12-24 14:48:50.831 RWReactivePlayground[9193:a0b] is th2013-12-24 14:48:50.878 RWReactivePlayground[9193:a0b] is thi2013-12-24 14:48:50.901 RWReactivePlayground[9193:a0b] is this2013-12-24 14:48:51.009 RWReactivePlayground[9193:a0b] is this 2013-12-24 14:48:51.142 RWReactivePlayground[9193:a0b] is this m2013-12-24 14:48:51.236 RWReactivePlayground[9193:a0b] is this ma2013-12-24 14:48:51.335 RWReactivePlayground[9193:a0b] is this mag2013-12-24 14:48:51.439 RWReactivePlayground[9193:a0b] is this magi2013-12-24 14:48:51.535 RWReactivePlayground[9193:a0b] is this magic2013-12-24 14:48:51.774 RWReactivePlayground[9193:a0b] is this magic?

可以看到每次改變文本框中的文字,block中的代碼都會執行。沒有target-action,沒有delegate,只有signal和block。令人激動不是嗎?

 ReactiveCocoa signal(RACSignal)發送事件流給它的subscriber。目前總共有三種類型的事件:nexterror、completed。一個signal在因error終止或者完成前,可以發送任意數量的next事件。在本教程的第一部分,我們將會關注next事件。在第二部分,將會學習error和completed事件。

 RACSignal有很多方法可以來訂閱不同的事件類型。每個方法都需要至少一個block,當事件發生時就會執行block中的邏輯。在上面的例子中可以看到每次next事件發生時,subscribeNext:方法提供的block都會執行。

 ReactiveCocoa框架使用category來為很多基本UIKit控件添加signal。這樣你就能給控件添加訂閱了,text field的rac_textSignal就是這么來的。

 原理就說這么多,是時候開始讓ReactiveCocoa干活了。

 ReactiveCocoa有很多操作來控制事件流。假設你只關心超過3個字符長度的用戶名,那么你可以使用filter操作來實現這個目的。把之前加在viewDidLoad中的代碼更新成下面的:

[[self.usernameTextField.rac_textSignalfilter:^BOOL(id value){   NSString*text = value;   return text.length > 3;}]subscribeNext:^(id x){   NSLog(@"%@", x);  }];

編譯運行,在text field只能怪輸入幾個字,你會發現只有當輸入超過3個字符時才會有log。

2013-12-26 08:17:51.335 RWReactivePlayground[9654:a0b] is t2013-12-26 08:17:51.478 RWReactivePlayground[9654:a0b] is th2013-12-26 08:17:51.526 RWReactivePlayground[9654:a0b] is thi2013-12-26 08:17:51.548 RWReactivePlayground[9654:a0b] is this2013-12-26 08:17:51.676 RWReactivePlayground[9654:a0b] is this 2013-12-26 08:17:51.798 RWReactivePlayground[9654:a0b] is this m2013-12-26 08:17:51.926 RWReactivePlayground[9654:a0b] is this ma2013-12-26 08:17:51.987 RWReactivePlayground[9654:a0b] is this mag2013-12-26 08:17:52.141 RWReactivePlayground[9654:a0b] is this magi2013-12-26 08:17:52.229 RWReactivePlayground[9654:a0b] is this magic2013-12-26 08:17:52.486 RWReactivePlayground[9654:a0b] is this magic?

剛才所創建的只是一個很簡單的管道。這就是響應式編程的本質,根據數據流來表達應用的功能。

 用圖形來表達就是下面這樣的:

 

從上面的圖中可以看到,rac_textSignal是起始事件。然后數據通過一個filter,如果這個事件包含一個長度超過3的字符串,那么該事件就可以通過。管道的最后一步就是subscribeNext:,block在這里打印出事件的值。

 filter操作的輸出也是RACSignal,這點先放到一邊。你可以像下面那樣調整一下代碼來展示每一步的操作。

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,這在術語上叫做連貫接口(fluent interface)。這個功能可以讓你直接構建管道,而不用每一步都使用本地變量。

注意:ReactiveCocoa大量使用block。如果你是block新手,你可能想看看Apple官方的block編程指南。如果你熟悉block,但是覺得block的語法有些奇怪和難記,你可能會想看看這個有趣又實用的網頁f*****gblocksyntax.com。

類型轉換

如果你之前把代碼分成了多個步驟,現在再把它改回來吧。。。。。。。。

[[self.usernameTextField.rac_textSignal  filter:^BOOL(id value){    NSString*text = value; // implicit cast    return text.length > 3;  }]  subscribeNext:^(id x){    NSLog(@"%@", x);  }];

在上面的代碼中,注釋部分標記了將id隱式轉換為NSString,這看起來不是很好看。幸運的是,傳入block的值肯定是個NSString,所以你可以直接修改參數類型,把代碼更新成下面的這樣的:

[[self.usernameTextField.rac_textSignal  filter:^BOOL(NSString*text){    return text.length > 3;  }]  subscribeNext:^(id x){    NSLog(@"%@", x);  }];

編譯運行,確保沒什么問題。

什么是事件呢?

到目前為止,本篇教程已經描述了不同的事件類型,但是還沒有說明這些事件的結構。有意思的是(?),事件可以包括任何事情。

 下面來展示一下,在管道中添加另一個操作。把添加在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);  }];

編譯運行,你會發現log輸出變成了文本的長度而不是內容。

2013-12-26 12:06:54.566 RWReactivePlayground[10079:a0b] 42013-12-26 12:06:54.725 RWReactivePlayground[10079:a0b] 52013-12-26 12:06:54.853 RWReactivePlayground[10079:a0b] 62013-12-26 12:06:55.061 RWReactivePlayground[10079:a0b] 72013-12-26 12:06:55.197 RWReactivePlayground[10079:a0b] 82013-12-26 12:06:55.300 RWReactivePlayground[10079:a0b] 92013-12-26 12:06:55.462 RWReactivePlayground[10079:a0b] 102013-12-26 12:06:55.558 RWReactivePlayground[10079:a0b] 112013-12-26 12:06:55.646 RWReactivePlayground[10079:a0b] 12

新加的map操作通過block改變了事件的數據。map從上一個next事件接收數據,通過執行block把返回值傳給下一個next事件。在上面的代碼中,map以NSString為輸入,取字符串的長度,返回一個NSNumber

 來看下面的圖片:

能看到map操作之后的步驟收到的都是NSNumber實例。你可以使用map操作來把接收的數據轉換成想要的類型,只要它是個對象。

 

注意:在上面的例子中text.length返回一個NSUInteger,是一個基本類型。為了將它作為事件的內容,NSUInteger必須被封裝。幸運的是Objective-C literal syntax提供了一種簡單的方法來封裝——@ (text.length)。

 

現在差不多是時候用所學的內容來更新一下ReactivePlayground應用了。你可以把之前的添加代碼都刪除了。。。。。。

創建有效狀態信號

首先要做的就是創建一些信號,來表示用戶名和密碼輸入框中的輸入內容是否有效。把下面的代碼添加到RWViewController.mviewDidLoad的最后面:

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應用了一個map轉換。輸出是一個用NSNumber封裝的布爾值。

 下一步是轉換這些信號,從而能為輸入框設置不同的背景顏色?;旧暇褪?,你訂閱這些信號,然后用接收到的值來更新輸入框的背景顏色。下面有一種方法:

[[validPasswordSignal  map:^id(NSNumber *passwordValid){    return[passwordValid boolValue] ? [UIColor clearColor]:[UIColor yellowColor];  }]  subscribeNext:^(UIColor *color){    self.passwordTextField.backgroundColor = color;  }];

(不要使用這段代碼,下面有一種更好的寫法!)

 從概念上來說,就是把之前信號的輸出應用到輸入框的backgroundColor屬性上。但是上面的用法不是很好。

 幸運的是,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宏允許直接把信號的輸出應用到對象的屬性上。RAC宏有兩個參數,第一個是需要設置屬性值的對象,第二個是屬性名。每次信號產生一個next事件,傳遞過來的值都會應用到該屬性上。

 你不覺得這種方法很好嗎?

 在編譯運行之前,找到updateUIState方法,把頭兩行刪掉。

self.usernameTextField.backgroundColor =     self.usernameIsValid ? [UIColor clearColor] : [UIColor yellowColor]; self.passwordTextField.backgroundColor =     self.passwordIsValid ? [UIColor clearColor] : [UIColor yellowColor];

這樣就把不相關的代碼刪掉了。

 編譯運行,可以發現當輸入內容無效時,輸入框看起來高亮了,有效時又透明了。

 現在的邏輯用圖形來表示就是下面這樣的。能看到有兩條簡單的管道,兩個文本信號,經過一個map轉為表示是否有效的布爾值,再經過一個map轉為UIColor,而這個UIColor已經和輸入框的背景顏色綁定了。

你是否好奇為什么要創建兩個分開的validPasswordSignalvalidUsernameSignal呢,而不是每個輸入框一個單獨的管道呢?(?)稍安勿躁,答案就在下面。

原文: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!

聚合信號

目前在應用中,登錄按鈕只有當用戶名和密碼輸入框的輸入都有效時才工作?,F在要把這里改成響應式的。

 現在的代碼中已經有可以產生用戶名和密碼輸入框是否有效的信號了——validUsernameSignalvalidPasswordSignal了。現在需要做的就是聚合這兩個信號來決定登錄按鈕是否可用。

 把下面的代碼添加到viewDidLoad的末尾:

RACSignal *signUpActiveSignal =  [RACSignal combineLatest:@[validUsernameSignal, validPasswordSignal]                    reduce:^id(NSNumber*usernameValid, NSNumber *passwordValid){                      return @([usernameValid boolValue]&&[passwordValid boolValue]);                    }];

上面的代碼使用combineLatest:reduce:方法把validUsernameSignalvalidPasswordSignal產生的最新的值聚合在一起,并生成一個新的信號。每次這兩個源信號的任何一個產生新值時,reduce block都會執行,block的返回值會發給下一個信號。

注意:RACsignal的這個方法可以聚合任意數量的信號,reduce block的參數和每個源信號相關。ReactiveCocoa有一個工具類RACBlockTrampoline,它在內部處理reduce block的可變參數。實際上在ReactiveCocoa的實現中有很多隱藏的技巧,值得你去看看。

 現在已經有了合適的信號,把下面的代碼添加到viewDidLoad的末尾。這會把信號和按鈕的enabled屬性綁定。

[signUpActiveSignal subscribeNext:^(NSNumber*signupActive){   self.signInButton.enabled =[signupActive boolValue]; }];

在運行之前,把以前的舊實現刪掉。把下面這兩個屬性刪掉。

@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、usernameTextFieldChangedpasswordTextFieldChanged方法刪掉。

最后確保把viewDidLoadupdateUIState的調用刪掉。

 編譯運行,看看登錄按鈕。當用戶名和密碼輸入有效時,按鈕就是可用的,和以前一樣。

 現在應用的邏輯就是下面這樣的:

上圖展示了一些重要的概念,你可以使用ReactiveCocoa來完成一些重量級的任務。

  • 分割——信號可以有很多subscriber,也就是作為很多后續步驟的源。注意上圖中那個用來表示用戶名和密碼有效性的布爾信號,它被分割成多個,用于不同的地方
  • 聚合——多個信號可以聚合成一個新的信號,在上面的例子中,兩個布爾信號聚合成了一個。實際上你可以聚合并產生任何類型的信號。

 這些改動的結果就是,代碼中沒有用來表示兩個輸入框有效狀態的私有屬性了。這就是用響應式編程的一個關鍵區別,你不需要使用實例變量來追蹤瞬時狀態。

響應式的登錄

應用目前使用上面圖中展示的響應式管道來管理輸入框和按鈕的狀態。但是按鈕按下的處理用的還是action,所以下一步就是把剩下的邏輯都替換成響應式的。

 在storyboard中,登錄按鈕的Touch Up Inside事件和RWViewController.m中的signInButtonTouched方法是綁定的。下面會用響應的方法替換,所以首先要做的就是斷開當前的storyboard action。

 打開Main.storyboard,找到登錄按鈕,按住ctrl鍵單擊,打開outlet/action連接框,然后點擊x來斷開連接。如果你找不到的話,下圖中紅色箭頭指示的就是刪除按鈕。

你已經知道了ReactiveCocoa框架是如何給基本UIKit控件添加屬性和方法的了。目前你已經使用了rac_textSignal,它會在文本發生變化時產生信號。為了處理按鈕的事件,現在需要用到ReactiveCocoa為UIKit添加的另一個方法,rac_signalForControlEvents。

 現在回到RWViewController.m,把下面的代碼添加到viewDidLoad的末尾:

[[self.signInButton   rac_signalForControlEvents:UIControlEventTouchUpInside]   subscribeNext:^(id x) {     NSLog(@"button clicked");   }];

上面的代碼從按鈕的UIControlEventTouchUpInside事件創建了一個信號,然后添加了一個訂閱,在每次事件發生時都會輸出log。

 編譯運行,確保的確有log輸出。按鈕只在用戶名和密碼框輸入有效時可用,所以在點擊按鈕前需要在兩個文本框中輸入一些內容。

 可以看到Xcode控制臺的輸出和下面的類似:

2013-12-28 08:05:10.816 RWReactivePlayground[18203:a0b] button clicked2013-12-28 08:05:11.675 RWReactivePlayground[18203:a0b] button clicked2013-12-28 08:05:12.605 RWReactivePlayground[18203:a0b] button clicked2013-12-28 08:05:12.766 RWReactivePlayground[18203:a0b] button clicked2013-12-28 08:05:12.917 RWReactivePlayground[18203:a0b] button clicked

現在按鈕有了點擊事件的信號,下一步就是把它和登錄流程連接起來。那么問題就來了,打開RWDummySignInService.h,看一下接口:

typedef void (^RWSignInResponse)(BOOL); @interface RWDummySignInService : NSObject - (void)signInWithUsername:(NSString *)username                  password:(NSString *)password                  complete:(RWSignInResponse)completeBlock;                  @end

這個service有3個參數,用戶名、密碼和一個完成回調block。這個block會在登錄成功或失敗時執行。你可以在按鈕點擊事件的subscribeNext: blcok里直接調用這個方法,但是為什么你要這么做?(?)

 

注意:本教程為了簡便使用了一個假的service,所以它不依賴任何外部API。但你現在的確遇到了一個問題,如何使用這些不是用信號表示的API呢?

創建信號

幸運的是,把已有的異步API用信號的方式來表示相當簡單。首先把RWViewController.m中的signInButtonTouched:刪掉。你會用響應式的的方法來替換這段邏輯。

 還是在RWViewController.m中,添加下面的方法:

- (RACSignal *)signInSignal {return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber){   [self.signInService      signInWithUsername:self.usernameTextField.text               password:self.passwordTextField.text               complete:^(BOOL success){                    [subscriber sendNext:@(success)];                    [subscriber sendCompleted];     }];   return nil;}];}

上面的方法創建了一個信號,使用用戶名和密碼登錄?,F在分解來看一下。

 上面的代碼使用RACSignalcreateSignal:方法來創建信號。方法的入參是一個block,這個block描述了這個信號。當這個信號有subscriber時,block里的代碼就會執行。

 block的入參是一個subscriber實例,它遵循RACSubscriber協議,協議里有一些方法來產生事件,你可以發送任意數量的next事件,或者用error/complete事件來終止。本例中,信號發送了一個next事件來表示登錄是否成功,隨后是一個complete事件。

 這個block的返回值是一個RACDisposable對象,它允許你在一個訂閱被取消時執行一些清理工作。當前的信號不需要執行清理操作,所以返回nil就可以了。

 可以看到,把一個異步API用信號封裝是多簡單!

 現在就來使用這個新的信號。把之前添加在viewDidLoad中的代碼更新成下面這樣的:

[[[self.signInButton   rac_signalForControlEvents:UIControlEventTouchUpInside]   map:^id(id x){     return[self signInSignal];   }]   subscribeNext:^(id x){     NSLog(@"Sign in result: %@", x);   }];

上面的代碼使用map方法,把按鈕點擊信號轉換成了登錄信號。subscriber輸出log。

 編譯運行,點擊登錄按鈕,查看Xcode的控制臺,等等,輸出的這是個什么鬼?

2014-01-08 21:00:25.919 RWReactivePlayground[33818:a0b] Sign in result:                                   <RACDynamicSignal: 0xa068a00> name: +createSignal:

沒錯,你已經給subscribeNext:的block傳入了一個信號,但傳入的不是登錄結果的信號。

 下圖展示了到底發生了什么:

當點擊按鈕時,rac_signalForControlEvents發送了一個next事件(事件的data是UIButton)。map操作創建并返回了登錄信號,這意味著后續步驟都會收到一個RACSignal。這就是你在subscribeNext:這步看到的。

 上面問題的解決方法,有時候叫做信號中的信號,換句話說就是一個外部信號里面還有一個內部信號。你可以在外部信號的subscribeNext:block里訂閱內部信號。不過這樣嵌套太混亂啦,還好ReactiveCocoa已經解決了這個問題。

信號中的信號

解決的方法很簡單,只需要把map操作改成flattenMap就可以了:

[[[self.signInButton   rac_signalForControlEvents:UIControlEventTouchUpInside]   flattenMap:^id(id x){     return[self signInSignal];   }]   subscribeNext:^(id x){     NSLog(@"Sign in result: %@", x);   }];

這個操作把按鈕點擊事件轉換為登錄信號,同時還從內部信號發送事件到外部信號。

 編譯運行,注意控制臺,現在應該輸出登錄是否成功了。

2013-12-28 18:20:08.156 RWReactivePlayground[22993:a0b] Sign in result: 02013-12-28 18:25:50.927 RWReactivePlayground[22993:a0b] Sign in result: 1

還不錯。

 現在已經完成了大部分的內容,最后就是在subscribeNext步驟里添加登錄成功后跳轉的邏輯。把代碼更新成下面的:

[[[self.signInButtonrac_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從登錄信號中取得結果,相應地更新signInFailureText是否可見。如果登錄成功執行導航跳轉。

 編譯運行,應該就能再看到可愛的小貓啦!喵~

你注意到這個應用現在有一些用戶體驗上的小問題了嗎?當登錄service正在校驗用戶名和密碼時,登錄按鈕應該是不可點擊的。這會防止用戶多次執行登錄操作。還有,如果登錄失敗了,用戶再次嘗試登錄時,應該隱藏錯誤信息。

 這個邏輯應該怎么添加呢?改變按鈕的可用狀態并不是轉換(map)、過濾(filter)或者其他已經學過的概念。其實這個就叫做“副作用”,換句話說就是在一個next事件發生時執行的邏輯,而該邏輯并不改變事件本身。

添加附加操作(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:是直接跟在按鈕點擊事件的后面。而且doNext: block并沒有返回值。因為它是附加操作,并不改變事件本身。

 上面的doNext: block把按鈕置為不可點擊,隱藏登錄失敗提示。然后在subscribeNext:block里重新把按鈕置為可點擊,并根據登錄結果來決定是否顯示失敗提示。

 之前的管道圖就更新成下面這樣的:

編譯運行,確保登錄按鈕的可點擊狀態和預期的一樣。

現在所有的工作都已經完成了,這個應用已經是響應式的啦。

 如果你中途哪里出了問題,可以下載最終的工程(依賴庫都有),或者在Github上找到這份代碼,教程中的每一次編譯運行都有對應的commit。

 

注意:在異步操作執行的過程中禁用按鈕是一個常見的問題,ReactiveCocoa也能很好的解決。RACCommand就包含這個概念,它有一個enabled信號,能讓你把按鈕的enabled屬性和信號綁定起來。你也許想試試這個類。

總結

希望本教程為你今后在自己的應用中使用ReactiveCocoa打下了一個好的基礎。你可能需要一些練習來熟悉這些概念,但就像是語言或者編程,一旦你夯實基礎,用起來也就很簡單了。ReactiveCocoa的核心就是信號,而它不過就是事件流。還能再更簡單點嗎?

 在使用ReactiveCocoa后,我發現了一個有趣的事情,那就是你可以用很多種不同的方法來解決同一個問題。你可以用教程中的例子試試,調整一下信號,改改信號的分割和聚合。

 ReactiveCocoa的主旨是讓你的代碼更簡潔易懂,這值得多想想。我個人認為,如果邏輯可以用清晰的管道、流式語法來表示,那就很好理解這個應用到底干了什么了。

 


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
在线精品国产欧美| 日本国产精品视频| 欧美激情久久久久久| 亚洲福利小视频| 亚洲国产精品人人爽夜夜爽| 亚洲精品久久久久久久久| 亚洲精品国产拍免费91在线| 欧美多人爱爱视频网站| 亚洲男人天堂手机在线| 亚洲丁香婷深爱综合| 国产精品一区久久久| 久久影视电视剧免费网站| 亚洲精品日韩丝袜精品| 欧美日韩一区二区三区在线免费观看| 亚洲综合精品一区二区| 色爱精品视频一区| 亚洲欧美福利视频| 久久久噜噜噜久噜久久| 秋霞成人午夜鲁丝一区二区三区| 亚洲va国产va天堂va久久| 国产成人精品亚洲精品| 日本免费一区二区三区视频观看| 中文字幕亚洲一区在线观看| 国产精品一区二区久久国产| 国产一区二区三区在线免费观看| 成人写真福利网| 亚洲欧美精品中文字幕在线| 亚洲人成欧美中文字幕| 亚洲精品国产综合区久久久久久久| 最近中文字幕mv在线一区二区三区四区| 日本高清视频精品| 日韩一中文字幕| 欧美日韩在线免费观看| 久久久久久久999精品视频| 欧美性极品xxxx做受| 亚洲老头同性xxxxx| 国产va免费精品高清在线| 欧美成人亚洲成人| 91沈先生作品| 中文字幕亚洲综合| 久久久久久中文| 欧美激情aaaa| 91久久精品国产91久久| 日韩精品在线私人| 欧美精品18videos性欧美| 欧美日韩综合视频网址| 亚洲精品电影在线| 欧美日韩国产精品一区二区三区四区| 欧美日韩综合视频| 在线看欧美日韩| 国产欧美日韩免费看aⅴ视频| 午夜免费在线观看精品视频| 91黑丝高跟在线| 中文字幕欧美精品在线| 亚洲国产成人在线播放| 国产精品99久久久久久久久久久久| 精品视频久久久久久久| 国产精品久久久久不卡| 日韩精品免费综合视频在线播放| 国产中文字幕91| 国产在线观看精品| 日韩中文av在线| 91精品国产自产在线观看永久| 亚洲成人av在线播放| 欧美日韩免费一区| 国产精品免费一区| 国产精品美乳在线观看| 欧美性色视频在线| 亚洲美女av在线播放| 日韩久久午夜影院| 97欧美精品一区二区三区| 毛片精品免费在线观看| 国产欧美一区二区白浆黑人| 日本一区二三区好的精华液| 中文字幕av一区二区| 亚洲精品日韩在线| 亚洲国产精彩中文乱码av在线播放| 色阁综合伊人av| 欧美天天综合色影久久精品| 国产精品成人va在线观看| 成人午夜激情免费视频| 亚洲人成网在线播放| 国产精品日韩专区| 98精品国产高清在线xxxx天堂| 91久久精品国产91性色| 欧美老女人性视频| 日韩在线欧美在线| 日韩精品在线观看一区二区| 国产精品欧美日韩久久| 亚洲高清久久久久久| 91禁外国网站| 欧美超级免费视 在线| 热re91久久精品国99热蜜臀| 日韩亚洲成人av在线| 日韩成人在线电影网| 国产精品美女呻吟| 国产女人精品视频| 深夜福利一区二区| 日韩视频永久免费观看| 亚洲香蕉成人av网站在线观看| 欧美日韩国产中文字幕| 97精品一区二区三区| 日本精品免费一区二区三区| 亚洲午夜色婷婷在线| 国产精品美乳一区二区免费| 秋霞av国产精品一区| 欧美制服第一页| 亚洲精品一区二区三区不| 国产精品av在线播放| 一区二区三欧美| 在线精品91av| 久久精品美女视频网站| 日韩成人免费视频| 亚洲国产黄色片| 欧美一区二区三区四区在线| 国产精品自拍小视频| 国产午夜精品理论片a级探花| 国产自产女人91一区在线观看| 色播久久人人爽人人爽人人片视av| 亚洲美女激情视频| 亚洲精品国产成人| 久久久在线视频| 国产精品久久久av久久久| 久久综合五月天| 国产精品成人一区二区| 日韩欧美在线观看| 亚洲欧美国产精品久久久久久久| 91老司机精品视频| 久久精品一偷一偷国产| 亚洲美女视频网站| 欧美精品video| 高清在线视频日韩欧美| 91视频88av| 国产精品观看在线亚洲人成网| 国内成人精品视频| 国产精品国语对白| 国产精品丝袜久久久久久不卡| 日韩中文有码在线视频| 国产一区二区三区在线观看视频| 国产精品v日韩精品| 久久精品久久精品亚洲人| 国产一区在线播放| 国产日韩在线视频| 久久久久久久久久国产精品| 欧美在线视频免费| 欧美成人免费小视频| 亚洲精品福利在线观看| 97在线视频观看| 日韩av一区二区在线观看| 国产精品日韩在线播放| 国产亚洲精品综合一区91| 68精品国产免费久久久久久婷婷| 色妞在线综合亚洲欧美| 久久精品视频播放| 国产精品自产拍在线观看中文| 亚洲第一二三四五区| 国模极品一区二区三区| 91精品啪aⅴ在线观看国产| 欧美成人免费播放| 日韩成人中文字幕在线观看| 91精品啪在线观看麻豆免费| 日韩精品在线播放| 国产激情久久久久| 久久99久久久久久久噜噜|