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