ReactiveCocoa Introduction(1/2)

ReactiveCocoa Tutorial – The Definitive Introduction: Part 1/2 的學習筆記react

Part 2/2 先放一下,之後再研究。c++

iOS開發目前所用的事件(Events)以及對應的響應模型:target-actions, delegates, KVO, callbacks 等等,各不同。ide

ReactiveCocoa則定義了一個事件的標準接口,使得它們更方便地串聯、過濾以及組合。使用以下概念函數

a)Functional Programming(將函數提高到first-class地位,能夠把函數做爲傳遞的參數)學習

b)Reactive Programming 專一於數據流的傳播和改變測試

因此Reactive Programming也叫Functional Reactive Programming(FRP) frameworkthis

再來一個概念, fluent interface,  相似於連續賦值的的method cascading/method chainingspa

c++的例子(wiki)以下, 每一個成員函數都返回this的引用的基礎上,最終使用接口的代碼能夠寫成以下形式:code

 // Fluent usage
 int main(int argc, char **argv) {
     FluentGlutApp(argc, argv)
         .withDoubleBuffer().withRGBA().withAlpha().withDepth()
         .at(200, 200).across(500, 500)
         .named("My OpenGL/GLUT App")
         .create();
 }

(Objective-C這樣連續調用,帶來的很差看的一面是會在最前面累計無數"[")orm

以上具體概念可查閱wiki站,如下爲教學內容,本次實例是將一個登陸驗證界面由傳統的改成Reactive的

1. rac_textSignal 生成一個RACSignal 做爲起點向它們的subscriber發送一系列事件,分類爲三種: next, error, completed.

好比下面的subscribeNext 便是next事件。這句完成的動做是,usernameTextField的內容一旦被改變,即調用subscribeNext:註冊的block,參數爲textField.text

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

2.  filter: 來支持條件過濾

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

3. map: 來轉義(不然每次傳出來的都是同一個類型, 這個例子裏是NSString*)

4. 用RAC(id, property)建立一個RACSignal的例子, validPasswordSignal參考上面。到這步,實現了輸入字母夠長則改變輸入框背景顏色的功能。

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

5. 合併信號combineLatest:, 用戶名跟密碼都合法了才能進行下一步操做(顯示登陸摁鈕)

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

6. 對於button的改造,準備登陸

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

7. 改造一個dummy service(判斷輸入是否合法,經過則進入下一屏) 因而

-(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;
  }];
}

createSignal丟進來的是這樣一個東西(block): (RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe

它是一個block, 返回值是RACDisposable *(大概是最後回收內存等清理工做?), block的參數是實現了RACSubscriber protocol的NSObject*

因此上面代碼倒數第三行須要return nil, nil表示不須要清理。block內部,執行完判斷後,給subscriber發送next 跟completed 事件。

8. signal of signals

參考下面的代碼,若是不是flattenMap: 而是用map:, 運行時出錯,丟出來的result: x是RACDynamicSignal

不是期待的@(success)合成的0或者1。 改完後,就能夠在subscribeNext的block中根據結果作對應的操做了。

[[[self.signInButton
   rac_signalForControlEvents:UIControlEventTouchUpInside]
   flattenMap:^id(id x) { // x = signButton
     return [self signInSignal];
   }]
   subscribeNext:^(id x) { // x = the signal if not flatternMap
     NSLog(@"Sign in result: %@", x);
   }];

9. 反作用/side effect

很細心的一個細節: 用戶摁了signin button後必須disable掉這個button防止重複摁(實際上是另外一個細節帶來的,爲了防止暴力測試,驗證過程當中會delay2s出結果,在這個過程當中,須要阻止屢次sign in請求)。 從signal的角度看,這個要求是side effect, 它並不改變事件自己。

因此引入 doNext: , 最後代碼是這樣的:

[[[[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];
     }
   }];

 

結論是ReactiveCocoa的代碼邏輯性很是強,什麼條件觸發什麼動做一目瞭然,避免了維護一堆記錄狀態的變量。確實炫。相似的入門描述還能夠參考

http://www.teehanlax.com/blog/reactivecocoa/

http://www.teehanlax.com/blog/getting-started-with-reactivecocoa/

這個教程的第二部分,是描述另外兩種事件類型: error跟completed, 以及throttling, threading,  continuations的,暫時感受距離目前代碼有點遙遠,不能直接利用,先擱置吧。

2014.04.11

相關文章
相關標籤/搜索