Reactive Cocoa Tutorial 系列,轉載請註明該文源地址 http://www.cnblogs.com/sunnyxx/p/3547763.html -- by sunnyxxhtml
上一篇介紹了函數式編程和RACStream,使得函數得以串聯起來,而它的具體子類,也是RAC編程中最重要的部分,RACSignal就是使得算式得以逐步運算並使其有意義的關鍵所在,本節主要介紹RACSignal的機理,具體的使用放到接下來的幾節。編程
RACStream實現了一個嵌套函數的結構,如f(x) = f1(f2(f3(x))),但好像是考試卷子上的一道題,沒有人去作它,沒得出個結果的話這道題是沒有意義的。網絡
OK,如今起將這個事兒都比喻成一個巧克力工廠,f(x)的結果是一塊巧克力,f1,f2,f3表明巧克力生產的幾個步驟,若是這個工廠不開工,它是沒有意義的。併發
再說RACSignal,引用RAC doc的描述:「A signal, represented by the RACSignal class, is a push-driven stream.」ide
我以爲這個「push-driven」要想解釋清楚,須要和RACSequence的「pull-driven」放在一塊兒來看。在巧克力工廠,push-driven是「生產一個吃一個」,而pull-driven是「吃完一個才生產下一個」,對於工廠來講前者是主動模式:生產了巧克力就「push」給各個供銷商,後者是被動模式:各個供銷商過來「pull」產品時纔給你現作巧克力。函數式編程
因此,對於RACSigna的push-driven的生產模式,首先,當工廠發現沒有供銷商籤合同準備要巧克力的時候,工廠固然沒有必要開動生產;只要當有一個以上準備收貨的經銷商時,工廠纔開動生產。這就是RACSignal的休眠(cold)和激活(hot)狀態,也就是所謂的冷信號和熱信號。通常狀況下,一個RACSignal建立以後都處於cold狀態,有人去subscribe才被激活。函數
RACSignal能產生且只能產生三種事件:next、completed,error。組件化
next表示這個Signal產生了一個值(成功生產了一塊巧克力)ui
completed表示Signal結束,結束信號只標誌成功結束,不帶值(一個批次的訂單完成了)spa
error表示Signal中出現錯誤,馬上結束(一個機器壞了,生產線馬上中止運轉)
工廠廠長存了全部供銷商的QQ,每當發生上面三件事情的一件時,都用QQ挨個兒發消息告訴他們,因而供銷商就能根據生產狀態決定要作點什麼。當訂單完成或者失敗後,廠長就會把這個供銷商的QQ刪了,之後發消息的時候也就不必通知他了。
RACSignal在被subscribe的時候可能會產生反作用,先舉個官方的栗子:
__block int aNumber = 0; // Signal that will have the side effect of incrementing `aNumber` block // variable for each subscription before sending it. RACSignal *aSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { aNumber++; [subscriber sendNext:@(aNumber)]; [subscriber sendCompleted]; return nil; }]; // This will print "subscriber one: 1" [aSignal subscribeNext:^(id x) { NSLog(@"subscriber one: %@", x); }]; // This will print "subscriber two: 2" [aSignal subscribeNext:^(id x) { NSLog(@"subscriber two: %@", x); }];
上面的signal在做用域外部引用了一個int變量,同時在signal的運算過程當中做爲next事件的值返回,這就形成了所謂的「反作用」,由於第二個訂閱者的訂閱而影響了輸出值。
個人理解來看,這個事兒作的就不太地道,一個正經的函數式編程中的函數是不該該由於進行了運算而致使後面運算的值不統一的。但對於實際應用的狀況來看也到無可厚非,好比用戶點擊了「登陸」按鈕,編程時把登陸這個業務寫爲一個login的RACSignal,固然,第一次調用登陸和再點一次第二次調用登陸的結果確定不同了。因此說RAC式編程減小了大部分對臨時狀態值的定義,但不是所有哦。
怎麼辦呢?我以爲最好的辦法就是「約定」,RAC design guide裏面介紹了對於一個signal的命名法則:
固然,也能夠multicast一個event,使得某些特殊的狀況來共享一個反作用,後面再具體講,先一個官方的簡單的栗子:
// This signal starts a new request on each subscription. RACSignal *networkRequest = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { AFHTTPRequestOperation *operation = [client HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id response) { [subscriber sendNext:response]; [subscriber sendCompleted]; } failure:^(AFHTTPRequestOperation *operation, NSError *error) { [subscriber sendError:error]; }]; [client enqueueHTTPRequestOperation:operation]; return [RACDisposable disposableWithBlock:^{ [operation cancel]; }]; }]; // Starts a single request, no matter how many subscriptions `connection.signal` // gets. This is equivalent to the -replay operator, or similar to // +startEagerlyWithScheduler:block:. RACMulticastConnection *connection = [networkRequest multicast:[RACReplaySubject subject]]; [connection connect]; [connection.signal subscribeNext:^(id response) { NSLog(@"subscriber one: %@", response); }]; [connection.signal subscribeNext:^(id response) { NSLog(@"subscriber two: %@", response); }];
當地一個訂閱者subscribeNext的時候觸發了AFNetworkingOperation的建立和執行,開始網絡請求,此時又來了個訂閱者訂閱這個Signal,按理說這個網絡請求會被「反作用」,從新發一遍,但作了上面的處理以後,這兩個訂閱者接收到了一樣的一個請求的內容。
RACScheduler是RAC裏面對線程的簡單封裝,事件能夠在指定的scheduler上分發和執行,不特殊指定的話,事件的分發和執行都在一個默認的後臺線程裏面作,大多數狀況也就不用動了,有一些特殊的signal必須在主線程調用,使用-deliverOn:能夠切換調用的線程。
但值得特殊瞭解的事實是:
However, RAC guarantees that no two signal events will ever arrive concurrently. While an event is being processed, no other events will be delivered. The senders of any other events will be forced to wait until the current event has been handled.
意思是訂閱者執行時的block必定非併發執行,也就是說不會執行到一半被另外一個線程進入,也意味着寫subscribeXXX block的時候不必作加鎖處理了。
RACSignal的廠子建好了,運行的模式也都想好了,剩下的就是巧克力的加工工藝了。
有了RACStream的嵌套和組裝的基礎,RACSignal得以使用組件化的工藝來一步步的加工巧克力,從可可,牛奶,糖等原料,混合到這種巧克力適用的液態巧克力,過濾,提純,冷卻,夾心,壓模,再到包裝,一個巧克力就產出了。對於不一樣種類的巧克力,好比酒心巧克力,也不過是把其中的某個組件替換成注入酒心罷了。
RACSignal的生產組件,也就是它的各式各樣的operation,一個具體業務邏輯的實現,其實也就是選擇合適operation按合適的順序組合起來。
還舉那個用戶在textFiled輸入並顯示到上面的label中的栗子:
RAC(self.outputLabel, text) = self.inputTextField.rac_textSignal;
如今需求變成「用戶輸入3個字母以上才輸出到label,當不足3個時顯示提示」,OK,好辦:
RAC(self.outputLabel, text) = [[self.inputTextField.rac_textSignal
startWith:@"key is >3"] // startWith 一開始返回的初始值
filter:^BOOL(NSString *value) {
return value.length > 3; // filter使知足條件的值才能傳出
}];
需求又增長成「當輸入sunny時顯示輸入正確」
RAC(self.outputLabel, text) = [[self.inputTextField.rac_textSignal
startWith:@"key is >3"] // startWith 一開始返回的初始值
filter:^BOOL(NSString *value) { // filter使知足條件的值才能傳出
return value.length > 3;
}]
map:(NSString *value) { // map將一個值轉化爲另外一個值輸出
return [value isEqualToString:@"sunny"] ? @"bingo!" : value;
}];
能夠看出,基本上一個業務邏輯通過分析後能夠拆解成一個個小RACSignal的組合,也就像生產巧克力的一道道工藝了。上面的栗子慢慢感受就像了一個簡陋的輸答案的框了。
接下來的幾節就具體介紹一下RACSignal的operation方法,RAC提供了不少操做方法,大概總結爲幾大類:過濾型、XXX型、XXX型,後面再慢慢道來。