rac初識

(這篇文章原來發布在 csdn ,如今 blog 遷移過來,並用 Markdown 從新排版以及修改)react

本文英文原文出自這篇文章 ,但我只是有選擇性的進行了翻譯。框架

rac 強調原子操做以及組裝。rac 基本上是創建在信號的基礎上的,也就是 RACSignal ,全部的操做都能轉成 RACSignal 來組裝操做。這篇文章主要從信號的角度進行介紹。ide

單個信號

rac入門最經典的一個例子就是一個登陸界面,以下:
圖片描述
要求只有當用戶名和密碼都知足的時候,高亮 sign in 按鈕。spa

要想實現這樣的功能,傳統的作法,你須要在 delegate 裏面監聽輸入文字的變化,並作校驗,這樣至少同一個邏輯的代碼是分散開的,並且還須要寫不少額外代碼,rac 裏一個很大的特點就是可以將讓代碼不分,rac 實現上面的功能將會很簡介,代碼以下:翻譯

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

一行代碼足矣。。。其中 self.usernameTextField.rac_textSignal 就是一個RACSignal , RAC 對許多基礎組建都封裝了 RACSignal ,並不須要咱們本身去建立。運行上面代碼,而後在 username 輸入框中連續輸入 3 個 d,輸出以下code

2016-02-19 20:37:42.309 ReactiveExample[71930:6364937] d
2016-02-19 20:37:42.582 ReactiveExample[71930:6364937] dd
2016-02-19 20:37:42.952 ReactiveExample[71930:6364937] ddd

是否是很簡單!orm

不只如此,若是你還想對用戶名進行校驗,好比要求用戶名的長度大於3,那麼,你只須要將上面的代碼改成以下便可:對象

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

輸出以下blog

2016-02-19 20:50:42.069 ReactiveExample[72046:6445227] dddd
2016-02-19 20:50:43.530 ReactiveExample[72046:6445227] ddddd
2016-02-19 20:50:44.858 ReactiveExample[72046:6445227] dddddd

當輸入的字符個數大於3的時候,纔會觸發輸出。是否是很神奇!圖片

不過在繼續瞭解以前,我須要向你們簡單介紹 RAC 中兩個基本的概念

  1. 信號,也就是 RACSignal 對象。

  2. 訂閱,如上面的 subscribeNext操做。

以下代碼

[filteredUsername subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];

filteredUsername是一個信號,subscribeNext 表示對信號 filteredUsername 進行了訂閱。這樣寫的結果是,當 filteredUsername 發出信號的時候,就會被訂閱者感知到。所以會輸出log。

rac 基本方法

filter

在上面在作長度大於3的判斷時,咱們用到了 filter 操做。filter是一個 rac 操做,它的做用是將知足條件的usernameSourceSignal信號轉化成了filteredUsername信號,rac 有很是多這種操做,有興趣的能夠查看起官網文檔。固然上面的代碼,你也能夠組合在一塊兒,以下

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

map

上面的 filter 只是一個過濾操做,其實產生的新信號 filteredUsername 本質上仍是usernameSourceSignal,只不過是知足必定條件的 usernameSourceSignal 。在rac中,你徹底能夠將一個信號轉化成一個完成不一樣的信號。見以下代碼

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

跟上面只有filter進行一樣的輸入,輸出結果以下

2016-02-19 21:03:14.344 ReactiveExample[72125:6500152] 4
2016-02-19 21:03:15.112 ReactiveExample[72125:6500152] 5
2016-02-19 21:03:15.806 ReactiveExample[72125:6500152] 6

注意對比會發現,這裏輸出的再也不是輸入的 dddd ddddd dddddd,而是 d 的個數了,如今已是一個徹底不一樣的信號了。這是由於咱們對 self.usernameTextField.rac_textSignal 進行了 map 操做,造成新的信號,而這個信號傳遞的是@(text.length),事實上,這裏咱們能夠傳遞任何對象。

兩個信號

上面只考慮了單個信號的狀況,如今咱們考慮兩個信號的狀況,見以下代碼:

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

如今咱們作一個考慮,當用戶名,或者密碼正確的時候,輸入框顯示 clearColor 不然顯示 yellowcolor。

單獨來看,好比只是對密碼作上訴校驗,也就是當輸入密碼的時候,輸入框根據輸入密碼的對錯顯示不一樣的顏色,代碼以下:

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

若是同時當將二者考慮在一塊兒,能夠以下

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 框架提供的一種宏,用於將信號的輸出直接賦值給綁定的對象。

這裏仍是單獨處理的,rac 的一大特色就是組裝。接下來介紹怎麼將這兩個信號綁定到一塊兒。回到最初的需求,咱們須要在當用戶名以及密碼同時有效的狀況下,高亮 sign in 按鈕。這裏須要用到 combine 操做。以下

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

combineLatest 的做用是將最近的 validUsernameSignal 以及 validPasswordSignal 信號結合起來。reduce 操做將這 combineLatest 起來的兩個信號結合成一個信號,這個信號傳遞的值,能夠根據這兩個信號分別發出的信號結合起來,組成一個新的值。所以,sign in 按鈕的高亮能夠根據以下方法來實現

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

當 sign in 按鈕高亮的時候,就能夠開始處理 sign in 按鈕的響應了,響應代碼以下

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

本身建立信號

前面使用的信號都是 RAC 框架自帶的,不少時候,咱們也須要建立屬於本身的信號,建立信號以下

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

這時,sign in 按鈕的響應代碼替換爲

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

flattenmap

當 sign in 按鈕被點擊的時候,signInButton 會發生一個信號,注意,在之前,咱們只傳遞了對象,而這裏傳遞了一個信號,會有什麼不一樣呢,這裏不會輸出一個值,而是會輸出一串地址,由於,這裏屬於信號的信號,而不是普通的信號,爲了正常輸出,咱們須要使用flattenmap,修改以下

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

flattenmap 與 map 的區別在於,flattenmap 會講 signal 裏面的值取出來,造成一個正常的 signal ,而 map 操做,若是碰到一個 signal 對象,它只是簡單的將signal 最爲一個新的 signal 的值封裝成一個信號的信號。

相關文章
相關標籤/搜索