(這篇文章原來發布在 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 中兩個基本的概念
信號,也就是 RACSignal 對象。
訂閱,如上面的 subscribeNext操做。
以下代碼
[filteredUsername subscribeNext:^(id x) { NSLog(@"%@", x); }];
filteredUsername是一個信號,subscribeNext 表示對信號 filteredUsername 進行了訂閱。這樣寫的結果是,當 filteredUsername 發出信號的時候,就會被訂閱者感知到。所以會輸出log。
在上面在作長度大於3的判斷時,咱們用到了 filter 操做。filter是一個 rac 操做,它的做用是將知足條件的usernameSourceSignal信號轉化成了filteredUsername信號,rac 有很是多這種操做,有興趣的能夠查看起官網文檔。固然上面的代碼,你也能夠組合在一塊兒,以下
[self.usernameTextField.rac_textSignal filter:^BOOL(NSString *text) { return text.length > 3; }] subscribeNext:^(id x) { NSLog(@"%@", x); }];
上面的 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); }];
當 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 的值封裝成一個信號的信號。