ReactiveCocoa之從小白到老鐵

第〇:寫在前面

1.什么是ReactiveCocoa?

Reactive: 響應(yīng)式的、函數(shù)式的。
Cocoa:蘋果開發(fā)框架名稱.
Reactive + Cocoa = 具備函(Gao)數(shù)(Bi)式(Ge)編程思想的開發(fā)框架.

2.啥是編程思想?

常見的編程思想有:

  • 面向過程:打開冰箱->把大象放冰箱里->關(guān)上冰箱門。
  • 面向?qū)ο螅簩ο蟮纳砀唧w重三圍是屬性;對象洗衣做飯看孩子是行為。佛曰:一切皆可幻化為對象,Everything is Object。
  • 鏈?zhǔn)骄幊蹋喝绻眠^Monsary肯定見過 ->
    make.centerX.and.centerY.equalTo(self).with.offset(0.0);
    其實質(zhì)是每個方法返回一個”返回值為自身且?guī)в幸粋€參數(shù)項“的Block,形成Block串聯(lián),也就是所謂的鏈?zhǔn)骄幊獭?/li>
  • 函數(shù)式編程:
    思想上,函數(shù)式編程可謂博大精深,建議看這篇文章了解: 傳送門
    形式上,函數(shù)是一等公民,常見函數(shù)(或閉包)作為參數(shù)或返回值傳遞、閉包嵌套、函數(shù)組合等形式。如:
[array enumerateObjectsUsingBlock:^(NSNumber *number, NSUInteger idx, BOOL *stop) {
    NSLog(@"%@",number);
}];
  • 響應(yīng)式編程:對于輸入的變動加以監(jiān)聽,響應(yīng)式地,每次產(chǎn)生對應(yīng)的輸出。

ReactiveCocoa具備函數(shù)式編程和響應(yīng)式編程思想,因此常被稱之為函數(shù)響應(yīng)式編程(Functional Reactive Programming)框架.

3.為啥用ReactiveCocoa?

因為它統(tǒng)一并簡化了蘋果原生框架大多場景,使得開發(fā)更為一致而且高效。
ReactiveCocoa統(tǒng)一了iOS開發(fā)中常用的Target、Delegate、KVO、通知、NSTimer、網(wǎng)絡(luò)異步回調(diào)等操作,并將相應(yīng)的事件處理代碼聚合在一處。

4.CocoaPod引入

由于ReactiveCocoa 3.0.0 版本及以下都是用純Objective-C語言,這篇文章基于純OC開發(fā),因此Podfile文件長這樣(建工程名字我這取的是RACDemo):

use_frameworks!
target 'RACDemo' do
    project 'RACDemo' 
    pod 'ReactiveCocoa', '~> 3.0.0’
end

第一:基礎(chǔ)概念

ReactiveCocoa的核心是攜帶信息的信號進(jìn)行傳遞和訂閱的過程。
簡要示意圖如下:

1.png

接下來將會介紹與此相關(guān)的四個大的概念:

  1. 信號源
  2. 訂閱者
  3. 回收站(RACDisposable)
  4. 調(diào)度器(上圖中未提及)

1.信號源

  • 信號的老祖宗 RACStream
    RACStream,顧名思義,就是指信號流。其繼承結(jié)構(gòu)如下圖:
2.png

通常情況下不直接使用RACStream,而是用其子類進(jìn)行信號傳遞。

  • 核心類RACSignal
    最常用的信號類,主要功能是創(chuàng)建信號、配置訂閱者來訂閱信號。
    信號在傳遞過程中包括正常傳遞、傳遞完成和傳遞失敗的狀態(tài),分別對應(yīng)下圖中next、completed和error。
3.png
  • 序列信號RACSequence
    指的是用來簡化OC中的集合操作的信號類

2.訂閱者

  • 為了獲取某個信號源中的值,通常需要對該信號源進(jìn)行訂閱,這就產(chǎn)生了訂閱者的概念。
  • 在ReactiveCocoa中所有實現(xiàn)了RACSubscriber協(xié)議的類都可以作為信號源的訂閱者。
    ReactiveCocoa源碼中RACSubscriber協(xié)議規(guī)定了四個要實現(xiàn)的方法:
@protocol RACSubscriber <NSObject>
@required

- (void)sendNext:(id)value;

- (void)sendError:(NSError *)error;

- (void)sendCompleted;

- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable;

@end

其中: -sendNext:-sendError:-sendCompleted方法分別接收信號next、error和 completed事件;-didSubscribeWithDisposable:方法用來接收代表某次訂閱的 RACDisposable(回收站)對象。

3.回收站(RACDisposable)

  • 在ReactiveCocoa中并沒有專門的類來代表一次完整的訂閱行為,而間接地使用RACDisposable來表示訂閱行為。
  • 訂閱完成或失敗時自動觸發(fā)回收機(jī)制,因為它主要做一些資源回收和垃圾清理的工作。
  • 可以通過RACDisposable的dispose方法主動取消訂閱行為,不再接受信號傳遞的信息。

4.調(diào)度器(RACScheduler)

RACScheduler是對GCD的簡單封裝,用來調(diào)度訂閱任務(wù)。

第二:核心信號之RACSignal

創(chuàng)建、訂閱、發(fā)送、回收一個信號

- (void)testCreateSignal {
    // 1.創(chuàng)建信號
    RACSignal *siganl = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // 每當(dāng)有訂閱者訂閱信號,就會調(diào)用此block。
        // 3.發(fā)送信號
        [subscriber sendNext:@"糖水"];
        [subscriber sendNext:@"鹽水"];
        [subscriber sendNext:@"辣椒水"];
        // 如果不再發(fā)送數(shù)據(jù),最好發(fā)送信號完成,內(nèi)部會自動調(diào)用[RACDisposable disposable]取消訂閱信號。
        // 5.訂閱完成
        [subscriber sendCompleted];
        return [RACDisposable disposableWithBlock:^{
            // 7.當(dāng)信號發(fā)送完成或者發(fā)送錯誤,就會自動執(zhí)行這個block,取消訂閱信號。
            // 執(zhí)行完Block后,當(dāng)前信號就不在被訂閱了。
            NSLog(@"信號被銷毀");
        }];
    }];
    // 2.訂閱信號,依次獲取步驟3中傳遞的信息
    // (此方法隱藏了兩件事:一是創(chuàng)建訂閱者RACSubscriber,二是為信號配置訂閱者并觸發(fā)訂閱任務(wù)。其目的是簡化信號訂閱邏輯)
    [siganl subscribeNext:^(id x) {
        // 4.每當(dāng)有信號sendNext: ,就會調(diào)用此block.
        NSLog(@"接收到數(shù)據(jù):%@",x);
    } error:^(NSError *error) {
        NSLog(@"信號發(fā)送失敗");
    } completed:^{
        // 6.第5步中[subscriber sendCompleted];執(zhí)行后直接走此處方法
        NSLog(@"信號發(fā)送完成");
    }];
    // 8.繼續(xù)訂閱信號,依次獲取步驟3中傳遞的信息
    [siganl subscribeNext:^(id x) {
        // 每當(dāng)有信號sendNext: ,就會調(diào)用此block.
        NSLog(@"接收到數(shù)據(jù):%@",x);
    }];
}

   // 最終控制臺輸出結(jié)果為:
    2017-06-23 16:15:49.027 RACDemo[48262:2367413] 第一個訂閱者接收到:糖水
    2017-06-23 16:15:49.027 RACDemo[48262:2367413] 第一個訂閱者接收到:鹽水
    2017-06-23 16:15:49.027 RACDemo[48262:2367413] 第一個訂閱者接收到:辣椒水
    2017-06-23 16:15:49.028 RACDemo[48262:2367413] 信號發(fā)送完成
    2017-06-23 16:15:49.028 RACDemo[48262:2367413] 信號被銷毀
    2017-06-23 16:15:49.028 RACDemo[48262:2367413] 第二個訂閱者接收到:糖水
    2017-06-23 16:15:49.028 RACDemo[48262:2367413] 第二個訂閱者接收到:鹽水
    2017-06-23 16:15:49.028 RACDemo[48262:2367413] 第二個訂閱者接收到:辣椒水
    2017-06-23 16:15:49.029 RACDemo[48262:2367413] 信號被銷毀

解讀: 對于信號創(chuàng)建、訂閱、發(fā)送、回收,我們可以把整個ReactiveCocoa框架形象地類比為水管管道機(jī)制:

  • 假裝你擁有一個空水管的整體管道(也就是CocoaReactive框架)
  • 信號(RACSignal)的創(chuàng)建猶如開啟一個入水閥閥門
  • 信號攜帶的信息……舉個栗子:糖水、鹽水、辣椒水
  • 創(chuàng)建信號(createSignal:),如同入水閥中陸續(xù)倒上這幾種水,擱著,等待流動(傳遞)(sendNext:)。
  • 訂閱者(RACSubscriber)相當(dāng)于一個出水閥,進(jìn)行訂閱(subscribeNext:)就是擰開出水閥,和被訂閱的入水閥(RACSignal)連通,這樣水就開始流動,進(jìn)行傳遞(sendNext:)。
  • 一旦水流抵達(dá)出水閥后不再繼續(xù)管道流通(sendCompleted:),或者水在半路堵死了(sendError:),這時候沒必要繼續(xù)保持出水閥和入水閥間的連通(或者說訂閱)狀態(tài)(節(jié)約用水,人人有責(zé)),會自動關(guān)閉出水閥閥門并阻斷連通,也就是觸發(fā)回收([RACDisposable disposableWithBlock:^{})行為解除訂閱,此時訂閱過程結(jié)束。
  • 回收行為的代碼往往在信號創(chuàng)建時一并書寫;回收(RACDisposable)代表著一次訂閱過程的結(jié)束。
  • 每個出水閥的訂閱(也就是每次擰開一個出水閥的閥門),都會立即接收到入水閥先后放進(jìn)去的信息(糖水、鹽水、辣椒水)。
  • 如果有多個出水閥(訂閱者)訂閱一個入水閥的情況,則第一個出水閥被回收(擰上)后,再打開下一個出水閥訂閱,結(jié)束后再被回收,再擰開下一個出水閥……一次擰開一個、依開啟順序進(jìn)行。

畫個圖來表示上面的代碼吧:

4.png

第三:華麗的信號家族之RACSubject

  • 上面的圖片描述了一般信號傳遞的機(jī)制,華麗版的信號傳遞無非是在此基礎(chǔ)上,修修水管管道,調(diào)整水閥間的連通關(guān)系等。
  • 由于水閥水管實用性強(qiáng),生活家居必備,嵌入在現(xiàn)實生活可以連通城市地下網(wǎng)絡(luò),嵌入在蘋果Cocoa框架中,更是將原來不靈動的框架硬生生靈動起來,尤其是上文已經(jīng)提到的Target、Delegate、KVO、通知、NSTimer、網(wǎng)絡(luò)異步回調(diào)等,被信號化后結(jié)構(gòu)便一致起來。

下面說說更多有趣的信號場景

1. RACSubject

RACSubject和RACSignal的最大不同是,RACSubject既是信號的子類,同時實現(xiàn)了訂閱者RACSubscriber協(xié)議,因此也可以充當(dāng)訂閱者發(fā)送信息,于是乎,看個栗子:

- (void)testRACSubject {
    // RACSubject使用步驟
    // 1.創(chuàng)建信號 [RACSubject subject],跟RACSiganl不一樣,創(chuàng)建信號時沒有block。
    // 2.訂閱信號 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
    // 3.發(fā)送信號 sendNext:(id)value
    
    // RACSubject:底層實現(xiàn)和RACSignal不一樣。
    // 1.調(diào)用subscribeNext訂閱信號,只是把訂閱者保存起來,并且訂閱者的nextBlock已經(jīng)賦值了。
    // 2.調(diào)用sendNext發(fā)送信號,遍歷剛剛保存的所有訂閱者,一個一個調(diào)用訂閱者的nextBlock。
    
    // 1.創(chuàng)建信號
    RACSubject *subject = [RACSubject subject];
    
    // 2.訂閱信號
    [subject subscribeNext:^(id x) {
        // block調(diào)用時刻:當(dāng)信號發(fā)出新值,就會調(diào)用.
        NSLog(@"第一個訂閱者%@",x);
    }];
    [subject subscribeNext:^(id x) {
        // block調(diào)用時刻:當(dāng)信號發(fā)出新值,就會調(diào)用.
        NSLog(@"第二個訂閱者%@",x);
    }];
    
    // 3.發(fā)送信號
    [subject sendNext:@"糖水"];
    [subject sendNext:@"鹽水"];
    [subject sendNext:@"辣椒水"];
}

    // 最終控制臺輸出結(jié)果為:
    2017-06-23 16:12:13.565 RACDemo[48194:2363636] 第一個訂閱者接收到:糖水
    2017-06-23 16:12:13.566 RACDemo[48194:2363636] 第二個訂閱者接收到:糖水
    2017-06-23 16:12:13.566 RACDemo[48194:2363636] 第一個訂閱者接收到:鹽水
    2017-06-23 16:12:13.566 RACDemo[48194:2363636] 第二個訂閱者接收到:鹽水
    2017-06-23 16:12:13.566 RACDemo[48194:2363636] 第一個訂閱者接收到:辣椒水
    2017-06-23 16:12:13.566 RACDemo[48194:2363636] 第二個訂閱者接收到:辣椒水
    

解讀:

  • 首先寫法上,sendNext:不用擠在信號創(chuàng)建的block中去寫了,因為RACSubject對象本身可以作為訂閱者調(diào)用此方法。
  • 其次,RACSubject的訂閱方法(subscribeNext:)僅僅是擰開出水閥,此時進(jìn)水閥中還沒倒進(jìn)水。
  • 當(dāng)主動倒水(sendNext:)時,會把本次放進(jìn)去的水傳遞給所有的出水閥
  • RACSubject和RACSignal最大的不同在于放水(sendNext:)的時機(jī),前者是配置好出水口后主動流向所有出水口,后者是出水口打開后將入水閥門中的水引出。

畫個圖吧:

5.png

2. RACReplaySubject

不要被更長的名字嚇到,其實它更簡單。如果說RACSubject是對出水管都統(tǒng)一配置好再送水的過程,那么RACReplaySubject相當(dāng)于把水流都配置好等待被出水管引出的過程。簡單的代碼走起:

- (void)testRACReplaySubject {
    // RACReplaySubject:底層實現(xiàn)和RACSubject不一樣。
    // 1.調(diào)用sendNext發(fā)送信號,把值保存起來,然后遍歷剛剛保存的所有訂閱者,一個一個調(diào)用訂閱者的nextBlock。
    // 2.調(diào)用subscribeNext訂閱信號,遍歷保存的所有值,一個一個調(diào)用訂閱者的nextBlock
    
    // 如果想當(dāng)一個信號被訂閱,就重復(fù)播放之前所有值,需要先發(fā)送信號,在訂閱信號。
    // 也就是先保存值,在訂閱值。
    
    // 1.創(chuàng)建信號
    RACReplaySubject *replaySubject = [RACReplaySubject subject];
    
    // 2.發(fā)送信號
    [replaySubject sendNext:@"糖水"];
    [replaySubject sendNext:@"鹽水"];
    [replaySubject sendNext:@"辣椒水"];
    
    // 3.訂閱信號
    [replaySubject subscribeNext:^(id x) {
        NSLog(@"第一個訂閱者接收到:%@",x);
    }];
    
    // 訂閱信號
    [replaySubject subscribeNext:^(id x) {
        NSLog(@"第一個訂閱者接收到:%@",x);
    }];
}

    // 最終控制臺輸出結(jié)果為:
    2017-06-26 17:52:39.670 RACDemo[61435:3788527] 第一個訂閱者接收到:糖水
    2017-06-26 17:52:39.671 RACDemo[61435:3788527] 第一個訂閱者接收到:鹽水
    2017-06-26 17:52:39.671 RACDemo[61435:3788527] 第一個訂閱者接收到:辣椒水
    2017-06-26 17:52:39.671 RACDemo[61435:3788527] 第二個訂閱者接收到:糖水
    2017-06-26 17:52:39.671 RACDemo[61435:3788527] 第二個訂閱者接收到:鹽水
    2017-06-26 17:52:39.672 RACDemo[61435:3788527] 第二個訂閱者接收到:辣椒水

解讀:

  • 輸出的結(jié)果和RACSignal完全一致,只是寫法上略有不同
  • 這意味著,繼承自RACSubject的RACReplaySubject實現(xiàn)了基本信號的功能
  • 但是,RACReplaySubject還可以在上面的訂閱代碼后面,繼續(xù)sendNext:,實現(xiàn)和RACSubject一樣的功能.(有興趣的童鞋可以在上面代碼最后加一句[replaySubject sendNext:@"別的水"];試一下,看看打印的結(jié)果)

對于上面的代碼,依舊畫個圖表示吧:

6.png

總結(jié): RACSubject實現(xiàn)每個水流群發(fā)到所有出水口,RACReplaySubject實現(xiàn)每個出水口接收所有水流。
擴(kuò)展: 有人會把RACSignal和RACSubject中控制水流方式的不同,分為冷信號熱信號這么一說。

  • 冷,即為高冷,你不訂閱(打開出水口)就沒辦法收到信號(水流)信息,這說的就是RACSignal
  • 熱,即為熱情,管你有木有訂閱,先送水再說,大不了沒人收到我的信號信息,這說的就是RACSubject。
  • 冷信號能否變成熱信號,熱情起來呢?可以。接下來說說RACMulticastConnection~

3. RACMulticastConnection

常規(guī)RACSignal被訂閱多次時,每次都要將創(chuàng)建信號的Block中所有代碼(包括sendNext:)方法執(zhí)行一遍,可否向RACSubject一樣,只sendNext:一次就發(fā)送給所有訂閱者,也就是所謂的冷信號轉(zhuǎn)變?yōu)闊嵝盘?看代碼:

- (void)testRACMulticastConnection {
    // RACMulticastConnection使用步驟:
    // 1.創(chuàng)建信號 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
    // 2.創(chuàng)建連接 RACMulticastConnection *connect = [signal publish];
    // 3.訂閱信號,注意:訂閱的不在是之前的信號,而是連接的信號。 [connect.signal subscribeNext:nextBlock]
    // 4.連接 [connect connect]
    
    // RACMulticastConnection底層原理:
    // 1.創(chuàng)建connect,connect.sourceSignal -> RACSignal(原始信號)  connect.signal -> RACSubject
    // 2.訂閱connect.signal,會調(diào)用RACSubject的subscribeNext,創(chuàng)建訂閱者,而且把訂閱者保存起來,不會執(zhí)行block。
    // 3.[connect connect]內(nèi)部會訂閱RACSignal(原始信號),并且訂閱者是RACSubject
    // 3.1.訂閱原始信號,就會調(diào)用原始信號中的didSubscribe
    // 3.2 didSubscribe,拿到訂閱者調(diào)用sendNext,其實是調(diào)用RACSubject的sendNext
    // 4.RACSubject的sendNext,會遍歷RACSubject所有訂閱者發(fā)送信號。
    // 4.1 因為剛剛第二步,都是在訂閱RACSubject,因此會拿到第二步所有的訂閱者,調(diào)用他們的nextBlock
    
    
    // 需求:假設(shè)在一個信號中發(fā)送請求,每次訂閱一次都會發(fā)送請求,這樣就會導(dǎo)致多次請求。
    // 解決:使用RACMulticastConnection就能解決.
    
    // RACMulticastConnection:解決重復(fù)請求問題
    // 1.創(chuàng)建信號
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"發(fā)送請求");
        [subscriber sendNext:@"糖水"];
        [subscriber sendNext:@"鹽水"];
        [subscriber sendNext:@"辣椒水"];
        return nil;
    }];
    // 2.創(chuàng)建連接
    RACMulticastConnection *connect = [signal publish];
    // 3.訂閱信號,
    // 注意:訂閱信號,也不能激活信號,只是保存訂閱者到數(shù)組,必須通過連接,當(dāng)調(diào)用連接,就會一次性調(diào)用所有訂閱者的sendNext:
    [connect.signal subscribeNext:^(id x) {
        NSLog(@"第一個訂閱者接收到:%@",x);
    }];
    [connect.signal subscribeNext:^(id x) {
        NSLog(@"第二個訂閱者接收到:%@",x);
    }];
    // 4.連接,激活信號
    [connect connect];
}

    // 最終控制臺輸出結(jié)果為:
    2017-06-26 18:44:55.057 RACDemo[61893:3825826] 發(fā)送請求
    2017-06-26 18:44:55.058 RACDemo[61893:3825826] 第一個訂閱者接收到:糖水
    2017-06-26 18:44:55.058 RACDemo[61893:3825826] 第二個訂閱者接收到:糖水
    2017-06-26 18:44:55.058 RACDemo[61893:3825826] 第一個訂閱者接收到:鹽水
    2017-06-26 18:44:55.058 RACDemo[61893:3825826] 第二個訂閱者接收到:鹽水
    2017-06-26 18:44:55.058 RACDemo[61893:3825826] 第一個訂閱者接收到:辣椒水
    2017-06-26 18:44:55.059 RACDemo[61893:3825826] 第二個訂閱者接收到:辣椒水

解讀:

  • RACMulticastConnection的廬山真面目依舊走RACSubject訂閱機(jī)制
  • 恩,不畫示意圖了,同RACSubject~

第四:輕松點(diǎn),聊聊那些被封裝好的簡單信號及使用

如果,我說如果,理解ReactiveCocoa的原理很為難,那么使用它卻極其簡單……

  1. 監(jiān)聽是否實現(xiàn)了某方法 之 rac_signalForSelector
    應(yīng)用場景:判斷某個代理是否實現(xiàn)某方法。
    或者,下面栗子中監(jiān)控某Button所在的VC中是否實現(xiàn)了點(diǎn)擊方法
if ([self rac_signalForSelector:@selector(clickTestButton:)]) {
    NSLog(@"點(diǎn)擊了測試按鈕");
}
  1. 替代KVO的rac_valuesAndChangesForKeyPath
    有了它,我已忘記原來的KVO長什么樣子
[[self rac_valuesAndChangesForKeyPath:@"testNum"    
 options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
    NSLog(@"new value info is %@", x);
}];

  1. 監(jiān)聽事件rac_signalForControlEvents,如點(diǎn)擊事件。
    話說,你還在為按鈕addTarget:action:forControlEvents:然后找個別的地兒寫實現(xiàn)方法嗎?
[[self.testButton rac_signalForControlEvents:UIControlEventTouchUpInside]    
 subscribeNext:^(id x) {
    NSLog(@"已經(jīng)實現(xiàn)點(diǎn)擊事件");
}];
  1. 監(jiān)聽通知rac_addObserverForName,如鍵盤<s>嘆氣</s>彈起的通知
[[[NSNotificationCenter defaultCenter]    
 rac_addObserverForName:UIKeyboardWillShowNotification object:nil]    
          subscribeNext:^(id x) {
    NSLog(@"鍵盤彈出");
}];
  1. 手勢信號 之 rac_gestureSignal
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] init];
[panGesture.rac_gestureSignal subscribeNext:^(id x) {
    NSLog(@"拖動手勢:%@", x);
}];
[self.view addGestureRecognizer:panGesture];
  1. UITextView或UITextField文字變化信號 之 rac_textSignal
[_textField.rac_textSignal subscribeNext:^(id x) {
    NSLog(@"當(dāng)前文字:%@", x);
}];

第四:升級篇 知識進(jìn)階

如果你覺著上面的知識足矣用來開發(fā)項目,理論上可以簡化代碼量,但應(yīng)對更復(fù)雜的邏輯還是too young too naive,因此,接下來會聊到:

  • RACSequence
  • RACCommand
  • RACSchedule
  • RACChannel
  • 信號轉(zhuǎn)換
  • 信號拼接
  • RAC常用宏

1. RACSequence

為了簡化OC語言中的集合類操作,發(fā)明了RACSequence。

原理:見ReactiveCocoa v2.5 源碼解析之架構(gòu)總覽 中的RACSequence部分。

實現(xiàn),舉個栗子:

- (void)testRACSequence {
    // 1.遍歷數(shù)組
    NSArray *numbers = @[@1, @2, @3, @4];
    // 這里其實是三步
    // 第一步: 把數(shù)組轉(zhuǎn)換成集合RACSequence,即numbers.rac_sequence
    // 第二步: 把集合RACSequence轉(zhuǎn)換RACSignal信號類,numbers.rac_sequence.signal
    // 第三步: 訂閱信號,激活信號,會自動把集合中的所有值,遍歷出來。
    [numbers.rac_sequence.signal subscribeNext:^(id x) {
       NSLog(@"%@",x);
    }];
    
    // 2.遍歷字典,遍歷出來的鍵值對會包裝成RACTuple(元組對象)
    NSDictionary *dict = @{@"name":@"ABC",@"age":@22};
    // 下面RACTuple元組的概念請自行查閱
    [dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
        // 解包元組,會把元組的值,按順序給參數(shù)里面的變量賦值
        RACTupleUnpack(NSString *key,NSString *value) = x;
        // 相當(dāng)于以下寫法
        // NSString *key = x[0];
        // NSString *value = x[1];
        NSLog(@"%@ %@", key, value);
}];

2. RACCommand

RACCommand是一個可以包裹一個信號,并對信號的執(zhí)行、完成、失敗狀態(tài)進(jìn)行把控的類。通常把異步網(wǎng)絡(luò)請求封裝成信號的形式,然后通過RACCommand包裹信號,實時反饋網(wǎng)絡(luò)請求狀態(tài)(請求中、請求成功、請求失敗)。
模擬網(wǎng)絡(luò)請求,舉個栗子:

/// 先在當(dāng)前VC中添加屬性:
@interface ViewController ()
@property (nonatomic, strong) RACCommand *command;
@end

/// 實現(xiàn)文件里寫入此示例代碼
- (void)testRACCommand {
    if (!_command) {
        _command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
            return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
                NSLog(@"開始請求網(wǎng)絡(luò)");
                // 假裝請求到數(shù)據(jù)
                [subscriber sendNext:@"請求到的數(shù)據(jù)"];
                // 請求完成
                [subscriber sendCompleted];
                return [RACDisposable disposableWithBlock:^{
                    NSLog(@"訂閱過程完成");
                }];
            }];
        }];
        
        // 監(jiān)控是否執(zhí)行中狀態(tài)
        [_command.executing subscribeNext:^(id x) {
            NSNumber *executing = (NSNumber *)x;
            NSString *stateStr = executing.boolValue? @"請求狀態(tài)" : @"未請求狀態(tài)";
            NSLog(@"%@", stateStr);
        }];
        
        // 監(jiān)控請求成功的狀態(tài)
        [_command.executionSignals.switchToLatest subscribeNext:^(id x) {
            NSLog(@"請求成功后,拿到數(shù)據(jù): %@", x);
        }];
        
        // 監(jiān)控請求失敗的狀態(tài)
        [_command.errors subscribeNext:^(id x) {
            NSLog(@"請求失敗: %@", x);
        }];
    }
    
    // 執(zhí)行操作 調(diào)用上面createSignal中的block操作
    [_command execute:nil];
}

    打印結(jié)果為:
    2017-06-27 17:10:21.672 RACDemo[65799:6318742] 未請求狀態(tài)
    2017-06-27 17:10:21.680 RACDemo[65799:6318742] 請求狀態(tài)
    2017-06-27 17:10:21.681 RACDemo[65799:6318742] 開始請求網(wǎng)絡(luò)
    2017-06-27 17:10:21.681 RACDemo[65799:6318742] 請求成功后,拿到數(shù)據(jù): 請求到的數(shù)據(jù)
    2017-06-27 17:10:21.682 RACDemo[65799:6318742] 訂閱過程完成
    2017-06-27 17:10:21.682 RACDemo[65799:6318742] 未請求狀態(tài)

解讀:

  • 這里RACCommand的創(chuàng)建使用initWithSignalBlock:方法,其參數(shù)是一個“帶有一個參數(shù)并返回信號”的block,因此直接在后面返回一個信號即可。信號的創(chuàng)建方式和前文一致。
  • 創(chuàng)建完RACCommand后,就可以用RACCommand的相關(guān)屬性看到當(dāng)前Signal的狀態(tài)。
  • 不同于使用信號訂閱的三種狀態(tài)"subscribeNext:"、"subscribeError:"和"subscribeCompleted",這里用RACCommand的"executionSignals"屬性代表成功拿到發(fā)送的信號,"errors"屬性表示失敗的信號。
  • 在執(zhí)行[_command execute:nil];前,RACCommand的"executing"屬性默認(rèn)是NO,一旦執(zhí)行,"executing"屬性就會變成YES狀態(tài),直到出現(xiàn)"sendCompleted"或"sendError:"情況時,"executing"屬性再次變成NO
  • 由于RACCommand對信號執(zhí)行狀態(tài)多了更細(xì)致的描述,因此往往適用于異步的操作,如網(wǎng)絡(luò)請求這種需要在請求狀態(tài)時展示HUD、請求成功時隱藏HUD、請求成功的數(shù)據(jù)用來刷新視圖的場景,統(tǒng)統(tǒng)可以寫到一塊一并被處理。
  • 這里的代碼只是模擬了網(wǎng)絡(luò)請求,真正開發(fā)時需要封裝現(xiàn)有的網(wǎng)絡(luò)請求框架,在請求到數(shù)據(jù)、請求失敗和請求成功時加入"sendNext:"、"sendError:"和"sendCompleted"代碼。

擴(kuò)展:
這篇文章詳細(xì)介紹了RACCommand原理和能干的事兒,可以在了解完本篇后面的知識后閱讀學(xué)習(xí)。
ReactiveCocoa基本組件:理解和使用RACCommand

3. RACScheduler

RACScheduler是對GCD的一層封裝,管理信號的線程任務(wù),其原理建議參考這篇文章:
ReactiveCocoa 中 RACScheduler是如何封裝GCD的,注意其中對deliverOn方法的描述。
RACScheduler用法相對簡單,后面介紹信號拼接時會代碼提及。

4. RACChannel

原理篇請移步:RAC 中的雙向數(shù)據(jù)綁定 RACChannel
平時,主要用RACChannel的子類RACChannelTerminal來實現(xiàn)UI和屬性的雙向綁定功能,即UI變化屬性跟隨變化,屬性變化UI也跟隨變化。舉個栗子:


@interface ViewController ()

@property (weak, nonatomic) IBOutlet UITextField *textField; // 輸入框
@property (nonatomic, copy) NSString *stringText;            // 輸入框顯示的文字

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    RACChannelTerminal *textFieldChannelT = self.textField.rac_newTextChannel;
    // 輸入框文本變化反應(yīng)到stringText屬性上
    RAC(self, stringText) = textFieldChannelT;  
    // stringText屬性對應(yīng)的文字訂閱到輸入框上  這樣就實現(xiàn)了雙向綁定
    [RACObserve(self, stringText) subscribe:textFieldChannelT]; 
}

擴(kuò)展:

系統(tǒng)框架還提供了以下可以返回RACChannelTerminal的方法:

  • NSUserDefaults 之 rac_channelTerminalForKey:
  • UIDatePicker 之 rac_newDateChannelWithNilValue:
  • UISegmentedControl 之 rac_newSelectedSegmentIndexChannelWithNilValue:
  • UISlider 之 rac_newValueChannelWithNilValue:

5. RAC常用宏

  • RAC(TARGET, [KEYPATH, [NIL_VALUE]]) 常用于給某個對象的某個屬性綁定信號值
舉個栗子:
// 每當(dāng)textField文本變化時,就會把文本信息傳遞給self的textFieldText屬性
RAC(self, textFieldText) = [self.textField.rac_textSignal takeUntil:self.rac_willDeallocSignal];

  • RACObserve(TARGET, KEYPATH) 監(jiān)聽某個對象的某個屬性,一旦屬性值發(fā)生變化,立馬激活信號。
如前面KVO的例子:
[[self rac_valuesAndChangesForKeyPath:@"testNum"    
 options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
    NSLog(@"new value info is %@", x);
}];

使用此宏可以改寫為:
[RACObserve(self, testNum) subscribeNext:^(id x) {
    NSLog(@"new value is %@", x);
}];

  • @weakify(obj)@strongify(obj) 為避免Block循環(huán)引用而生的一對宏
通常,blobk引用其外部變量,為避免持有外部對象,通常使用這一對宏來避免,如:

@weakify(self);
[RACObserve(self, testNum) subscribeNext:^(id x) {
    @strongify(self);
    NSLog(@"new value is %@", self.x);
}];

  • RACTurplePackRACTupleUnpack分別代表打包生成元組 和 解包分解元組。
// RACTuplePack生成元組
RACTuple *tuple = RACTuplePack(@"數(shù)據(jù)1", @2);
// RACTupleUnpack分解元組 參數(shù):被解析的變量名
RACTupleUnpack(NSString *str, NSNumber *num) = tuple;
NSLog(@"%@ %@", str, num);

  • RACChannelTo 雙向綁定終端
一般情況下,使用RACChannelTo(self, name) = RACChannelTo(self.model, name);即可完成雙向等值綁定功能,即一方變化另一方也跟隨變化,但前提是觸發(fā)屬性的KVO機(jī)制才能實現(xiàn)。


二般情況下,舉個栗子:

//首先在VC中添加兩個屬性:
@property (nonatomic, copy) NSString *valueA;
@property (nonatomic, copy) NSString *valueB;

// 然后添加測試方法
- (void)testRACChannelTo:(id)sender {
    RACChannelTerminal *channelA = RACChannelTo(self, valueA);
    RACChannelTerminal *channelB = RACChannelTo(self, valueB);
    [[channelA map:^id(NSString *value) {
        if ([value isEqualToString:@"西"]) {
            return @"東";
        }
        return value;
    }] subscribe:channelB];
    [[channelB map:^id(NSString *value) {
        if ([value isEqualToString:@"左"]) {
            return @"右";
        }
        return value;
    }] subscribe:channelA];
    [[RACObserve(self, valueA) filter:^BOOL(id value) {
        return value ? YES : NO;
    }] subscribeNext:^(NSString* x) {
        NSLog(@"你向%@", x);
    }];
    [[RACObserve(self, valueB) filter:^BOOL(id value) {
        return value ? YES : NO;
    }] subscribeNext:^(NSString* x) {
        NSLog(@"他向%@", x);
    }];
    self.valueA = @"西";
    self.valueB = @"左";
}

輸出結(jié)果為:
2017-06-30 17:19:50.125 RACDemo[78483:7464849] 你向西
2017-06-30 17:19:50.126 RACDemo[78483:7464849] 他向東
2017-06-30 17:19:50.126 RACDemo[78483:7464849] 他向左
2017-06-30 17:19:50.127 RACDemo[78483:7464849] 你向右

6. 信號轉(zhuǎn)換

  • map 轉(zhuǎn)換信號值,返回新信號
- (void)testMap {
    RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"石"];
        return nil;
    }] map:^id(NSString* value) {
        if ([value isEqualToString:@"石"]) {
            return @"金";
        }
        return value;
    }];
    [signal subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
}

輸出結(jié)果為:
2017-06-29 10:35:41.914 RACDemo[69180:6961894] 金

  • filter 過濾
- (void)testFilter {
    [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@(15)];
        [subscriber sendNext:@(17)];
        [subscriber sendNext:@(21)];
        [subscriber sendNext:@(14)];
        [subscriber sendNext:@(30)];
        return nil;
    }] filter:^BOOL(NSNumber* value) {
        return value.integerValue >= 18;
    }] subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
}

輸出結(jié)果為:
2017-06-29 10:39:13.718 RACDemo[69242:6964994] 21
2017-06-29 10:39:13.718 RACDemo[69242:6964994] 30

  • flattenMap 直接轉(zhuǎn)成新的信號
- (void)testFlattenMap {
    [[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"打蛋液");
        [subscriber sendNext:@"蛋液"];
        [subscriber sendCompleted];
        return nil;
    }] flattenMap:^RACStream *(NSString* value) {
        return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            NSLog(@"把%@倒進(jìn)鍋里面煎",value);
            [subscriber sendNext:@"煎蛋"];
            [subscriber sendCompleted];
            return nil;
        }];
    }] flattenMap:^RACStream *(NSString* value) {
        return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            NSLog(@"把%@裝到盤里", value);
            [subscriber sendNext:@"上菜"];
            [subscriber sendCompleted];
            return nil;
        }];
    }] subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
}

輸出結(jié)果為:
2017-06-29 11:02:05.035 RACDemo[69369:6975870] 打蛋液
2017-06-29 11:02:05.036 RACDemo[69369:6975870] 把蛋液倒進(jìn)鍋里面煎
2017-06-29 11:02:05.036 RACDemo[69369:6975870] 把煎蛋裝到盤里
2017-06-29 11:02:05.036 RACDemo[69369:6975870] 上菜

  • take 只取前幾個訂閱
- (void)testTake {
    RACSubject *subject = [RACSubject subject];
    //只取前兩次的信號值
    [[subject take:2] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    [subject sendNext:@"1"];
    [subject sendNext:@"2"];
    [subject sendNext:@"3"];
    [subject sendNext:@"4"];
}

輸出結(jié)果為:
2017-06-29 11:41:22.418 RACDemo[69701:7001833] 1
2017-06-29 11:41:22.418 RACDemo[69701:7001833] 2

  • takeLast 只取最后幾個訂閱
- (void)testTakeLast {
    RACSubject *subject = [RACSubject subject];
    //只取前兩次的信號值
    [[subject takeLast:2] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    [subject sendNext:@"1"];
    [subject sendNext:@"2"];
    [subject sendNext:@"3"];
    [subject sendNext:@"4"];
    // 注意此處必須訂閱完成 才能使用takeLast進(jìn)行最后幾次訂閱的統(tǒng)計
    [subject sendCompleted];
}

輸出結(jié)果為:
2017-06-29 11:48:15.642 RACDemo[69834:7008684] 3
2017-06-29 11:48:15.642 RACDemo[69834:7008684] 4

  • skip 先跳過幾個訂閱
// 默認(rèn)UITextField創(chuàng)建時就能訂閱到其信號,往往會跳過第一次信號
[[self.textfield.rac_textSignal skip:1] subscribeNext:^(id x) {
    NSLog(@"%@",x);
}];
  • ignore 內(nèi)部調(diào)用filter過濾,忽略掉ignore的值
- (void)testIgnore {
    RACSubject *subject = [RACSubject subject];
    //只取前兩次的信號值
    [[subject ignore:@"2"] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    [subject sendNext:@"1"];
    [subject sendNext:@"2"];
    [subject sendNext:@"3"];
    [subject sendNext:@"4"];
}

輸出結(jié)果為:
2017-06-29 12:06:56.999 RACDemo[69907:7018889] 1
2017-06-29 12:06:57.000 RACDemo[69907:7018889] 3
2017-06-29 12:06:57.000 RACDemo[69907:7018889] 4 

  • distinctUntilChanged 只訂閱當(dāng)前信號值不同的信號
- (void)testDistinctUntilChanged {
    RACSubject *subject = [RACSubject subject];
    //當(dāng)上一次的值和這次的值有明顯變化的時候就會發(fā)出信號,否則會忽略掉
    //一般用來刷新UI界面,當(dāng)數(shù)據(jù)有變化的時候才會刷新
    [[subject distinctUntilChanged] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    [subject sendNext:@"A"];
    [subject sendNext:@"A"];
    [subject sendNext:@"B"];
}

輸出結(jié)果為:
2017-06-29 15:39:03.417 RACDemo[70120:7083874] A
2017-06-29 15:39:03.418 RACDemo[70120:7083874] B

  • switchTolatest 獲取信號組中的信號
如上面RACCommand的代碼栗子中:
// 監(jiān)控請求成功的狀態(tài)
[_command.executionSignals.switchToLatest subscribeNext:^(id x) {
    NSLog(@"請求成功后,拿到數(shù)據(jù): %@", x);
}];

這里的executionSignals返回的是信號組(本身也是信號類型), RACCommand每`excute:`一次就會向信號組中扔進(jìn)去新的信號,這里我們先拿到信號組,然后用switchToLatest屬性拿到這個最新的信號,最后再進(jìn)行訂閱。

7. 信號拼接

  • concat 拼接。注意:在signalA后拼接signalB,只有signalA發(fā)送完成sendCompleted,signalB才會被拼接。
- (void)testConcat {
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"我戀愛啦"];
        [subscriber sendCompleted];
        return nil;
    }];
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"我結(jié)婚啦"];
        [subscriber sendCompleted];
        return nil;
    }];
    [[signalA concat:signalB] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
}

輸出結(jié)果為:
2017-06-30 10:47:36.654 RACDemo[75454:7236811] 我戀愛啦
2017-06-30 10:47:36.655 RACDemo[75454:7236811] 我結(jié)婚啦

  • then 轉(zhuǎn)接。連接下一個信號,當(dāng)前信號完成后,才連接then后的信號。
- (void)testThen {
    // then:用于連接兩個信號,當(dāng)?shù)谝粋€信號完成,才會連接then返回的信號
    // 注意使用then,之前信號的值會被忽略掉.
    // 底層實現(xiàn):1、先過濾掉之前的信號發(fā)出的值。2.使用concat連接then返回的信號
    [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@1];
        // 這里必須sendCompleted才能執(zhí)行then后的信號
        [subscriber sendCompleted];
        return nil;
    }] then:^RACSignal *{
        return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@2];
            return nil;
        }];
    }] subscribeNext:^(id x) {
        // 只能接收到第二個信號的值,也就是then返回信號的值
        NSLog(@"%@",x);
    }];
}

輸出結(jié)果為:
2017-06-30 10:44:08.578 RACDemo[75394:7233518] 2

  • merge 合并。任何一個信號有新值的時候就會被訂閱。
- (void)testMerge {
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"紙廠污水"];
        return nil;
    }];
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"電鍍廠污水"];
        return nil;
    }];
    [[RACSignal merge:@[signalA, signalB]] subscribeNext:^(id x) {
        NSLog(@"處理%@",x);
    }];
}

輸出結(jié)果為:
2017-06-30 10:55:58.645 RACDemo[75697:7244204] 處理紙廠污水
2017-06-30 10:55:58.645 RACDemo[75697:7244204] 處理電鍍廠污水

  • zip(同步合并壓縮) + reduce(分解元組)
- (void)testZip {
    RACSubject *letters = [RACSubject subject];
    RACSubject *numbers = [RACSubject subject];
    
    RACSignal *zipSignal = [RACSignal zip:@[letters,numbers] reduce:^id(NSString *letter,NSString *number){
        return [letter stringByAppendingString:number];
    }];
    [zipSignal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    [letters sendNext:@"A"];
    [letters sendNext:@"B"];
    [numbers sendNext:@"1"];
    [numbers sendNext:@"2"];
    [numbers sendNext:@"3"];
    [letters sendNext:@"C"];
    [letters sendNext:@"D"];
    [numbers sendNext:@"4"];
}

輸出結(jié)果為:
2017-06-30 11:03:50.907 RACDemo[75837:7251093] A1
2017-06-30 11:03:50.908 RACDemo[75837:7251093] B2
2017-06-30 11:03:50.908 RACDemo[75837:7251093] C3
2017-06-30 11:03:50.908 RACDemo[75837:7251093] D4

/// zip后發(fā)出的信號值是元組類型,因此這里使用 reduce: 方法直接分解元素,拿到元組中對應(yīng)的值
/// zip的合并行為是按順序取出各個信號然后合并發(fā)出的
/// 也就是說,letters的第一個值A(chǔ)和number的第一個值1合并輸出A1,第二個值B和number的第二個值2合并輸出B2       
/// 假設(shè)D后面,還有E,F,G...,但是沒有對應(yīng)的number信號,zip的合并行為就無法進(jìn)行下去了.

  • combineLatest(最新值合并壓縮) + reduce(分解元組)
- (void)testCombineLatest{
    RACSubject *letters = [RACSubject subject];
    RACSubject *numbers = [RACSubject subject];
    
    RACSignal *combined = [RACSignal combineLatest:@[letters,numbers] reduce:^id(NSString *letter,NSString *number){
        return [letter stringByAppendingString:number];
    }];
    [combined subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
    
    [letters sendNext:@"A"];
    [letters sendNext:@"B"];
    [numbers sendNext:@"1"];
    [numbers sendNext:@"2"];
    [letters sendNext:@"C"];
    [numbers sendNext:@"3"];
    [letters sendNext:@"D"];
    [numbers sendNext:@"4"];
}

輸出結(jié)果為:
2017-06-30 11:12:59.470 RACDemo[75910:7256870] B1
2017-06-30 11:12:59.471 RACDemo[75910:7256870] B2
2017-06-30 11:12:59.471 RACDemo[75910:7256870] C2
2017-06-30 11:12:59.471 RACDemo[75910:7256870] C3
2017-06-30 11:12:59.471 RACDemo[75910:7256870] D3
2017-06-30 11:12:59.472 RACDemo[75910:7256870] D4

/// combineLatest后發(fā)出的信號值是元組類型,因此這里使用 reduce: 方法直接分解元素,拿到元組中對應(yīng)的值
/// 上面代碼可以發(fā)現(xiàn):letter信號的A值被更新的B值覆蓋了,所以接下來接收到number信號的1時候,合并,輸出信號B1.
/// 當(dāng)接收到C的時候,與number的最新的值2合并,輸出信號C2.

  • takeUntil 獲取信號直到某個信號開始執(zhí)行
- (void)testTakeUntil {
    [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(id x) {
            [subscriber sendNext:@"直到世界的盡頭才能把我們分開"];
        }];
        return nil;
    }] takeUntil:[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"世界的盡頭到了");
            [subscriber sendNext:@"世界的盡頭到了"];
        });
        return nil;
    }]] subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
}

輸出結(jié)果為:
2017-06-30 11:46:55.103 RACDemo[75983:7272054] 直到世界的盡頭才能把我們分開
2017-06-30 11:46:56.103 RACDemo[75983:7272054] 直到世界的盡頭才能把我們分開
2017-06-30 11:46:57.103 RACDemo[75983:7272054] 直到世界的盡頭才能把我們分開
2017-06-30 11:46:58.103 RACDemo[75983:7272054] 直到世界的盡頭才能把我們分開
2017-06-30 11:46:59.102 RACDemo[75983:7272054] 世界的盡頭到了

  • doNext、doCompleted、doError 分別在sendNext、sendConpleted和sendError前執(zhí)行操作
- (void)doNextOrComleted {
    [[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@1];
        [subscriber sendCompleted];
        return nil;
    }] doNext:^(id x) {
        // 執(zhí)行[subscriber sendNext:@1];之前會調(diào)用這個Block
        NSLog(@"doNext");;
    }] doCompleted:^{
        // 執(zhí)行[subscriber sendCompleted];之前會調(diào)用這個Block
        NSLog(@"doCompleted");;
    }] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
}

輸出結(jié)果為:
2017-06-30 11:54:39.750 RACDemo[76163:7277651] doNext
2017-06-30 11:54:39.751 RACDemo[76163:7277651] 1
2017-06-30 11:54:39.751 RACDemo[76163:7277651] doCompleted

  • rac_liftSelector 每個信號至少sendNext一次后,拿到每個信號最新的信號值,然后執(zhí)行selector中的操作
- (void)testLiftSelector {
    // 當(dāng)多次數(shù)據(jù)請求時,需要全部請求結(jié)束之后才會進(jìn)行下一步UI刷新等,可以用這個rac_liftSelector
    // 第一部分?jǐn)?shù)據(jù)
    RACSignal *section01Signal01 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"section01數(shù)據(jù)請求");
        [subscriber sendNext:@"section01請求到的數(shù)據(jù)"];
        return nil;
    }];
    // 第二部分?jǐn)?shù)據(jù)
    RACSignal *section01Signal02 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"section02數(shù)據(jù)請求");
        [subscriber sendNext:@"section02請求到的數(shù)據(jù)"];
        return nil;
    }];
    // 第三部分?jǐn)?shù)據(jù)
    RACSignal *section01Signal03 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"section03數(shù)據(jù)請求");
        [subscriber sendNext:@"section03請求到的數(shù)據(jù)"];
        return nil;
    }];
    // 可以在所有信號請求完成之后再執(zhí)行方法,只需把三個結(jié)果傳出去即可
    // 注意下面@selector中的參數(shù)數(shù)量和傳遞和信號數(shù)量是一致的
    [self rac_liftSelector:@selector(refreshUI:::) withSignals:section01Signal01,section01Signal02,section01Signal03, nil];
}

- (void)refreshUI:(NSString *)str1 :(NSString *)str2 :(NSString *)str3 {
    NSLog(@"最終數(shù)據(jù)為:\n%@\n%@\n%@", str1, str2, str3);
}

輸出結(jié)果為:
2017-06-30 12:07:43.382 RACDemo[76282:7286396] section01數(shù)據(jù)請求
2017-06-30 12:07:43.382 RACDemo[76282:7286396] section02數(shù)據(jù)請求
2017-06-30 12:07:43.382 RACDemo[76282:7286396] section03數(shù)據(jù)請求
2017-06-30 12:07:43.383 RACDemo[76282:7286396] 最終數(shù)據(jù)為:
section01請求到的數(shù)據(jù)
section02請求到的數(shù)據(jù)
section03請求到的數(shù)據(jù)

8. 信號時間或頻率控制

  • timeOut 超時。超時一定時間后自動sendError
- (void)testTimeOut {
    RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"100"];
        return nil;
    }] timeout:1 onScheduler: [RACScheduler currentScheduler]]; // 這里將時間操作放在當(dāng)前線程中執(zhí)行
    
    [signal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    } error:^(NSError *error) {
        NSLog(@"1秒后會自動調(diào)用");
    }];
}

輸出結(jié)果為:
2017-06-30 12:20:41.715 RACDemo[76418:7295742] 100
2017-06-30 12:20:42.812 RACDemo[76418:7295742] 1秒后會自動調(diào)用

  • delay 延遲。延遲一定時間后再進(jìn)行信息傳遞
- (void)testDelay {
    [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"等等我,我還有3秒鐘就到了");
        [subscriber sendNext:nil];
        [subscriber sendCompleted];
        return nil;
    }] delay:3] subscribeNext:^(id x) {
        NSLog(@"我到了");
    }];
}

輸出結(jié)果為:
2017-06-30 12:27:15.584 RACDemo[76593:7301745] 等等我,我還有3秒鐘就到了
2017-06-30 12:27:18.884 RACDemo[76593:7301745] 我到了

  • replay 同RACReplaySubject實現(xiàn)原理一樣,將要傳遞的值打包保存一次,然后依次派送給訂閱者。
- (void)testReplay {
    RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@1];
        [subscriber sendNext:@2];
        return nil;
    }] replay];  // 使用replay后上面block中的代碼只執(zhí)行一次
    
    [signal subscribeNext:^(id x) {
        NSLog(@"第一個訂閱者%@",x);
    }];
    
    [signal subscribeNext:^(id x) {
        NSLog(@"第二個訂閱者%@",x);
    }];
}

輸出結(jié)果為:
2017-06-30 14:39:10.989 RACDemo[76768:7350292] 第一個訂閱者1
2017-06-30 14:39:11.778 RACDemo[76768:7350292] 第一個訂閱者2
2017-06-30 14:39:12.394 RACDemo[76768:7350292] 第二個訂閱者1
2017-06-30 14:39:13.068 RACDemo[76768:7350292] 第二個訂閱者2

  • retry 錯誤重試。訂閱者sendError后重新執(zhí)行信號block操作
- (void)testRetry {
    __block int failedCount = 0;
    [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        if (failedCount < 5) {
            failedCount++;
            NSLog(@"我失敗了");
            [subscriber sendError:nil];
        }else{
            NSLog(@"經(jīng)歷了五次失敗后");
            [subscriber sendNext:nil];
        }
        return nil;
    }] retry] subscribeNext:^(id x) {
        NSLog(@"終于成功了");
    }];
}

輸出結(jié)果為:
2017-06-30 14:47:19.473 RACDemo[76899:7358317] 我失敗了
2017-06-30 14:47:19.474 RACDemo[76899:7358317] 我失敗了
2017-06-30 14:47:19.475 RACDemo[76899:7358317] 我失敗了
2017-06-30 14:47:19.475 RACDemo[76899:7358317] 我失敗了
2017-06-30 14:47:19.475 RACDemo[76899:7358317] 我失敗了
2017-06-30 14:47:19.475 RACDemo[76899:7358317] 經(jīng)歷了五次失敗后
2017-06-30 14:47:19.475 RACDemo[76899:7358317] 終于成功了

另外:
- (RACSignal *)retry:(NSInteger)retryCount; 方法可以控制重試的次數(shù)。

  • interval 定時。每隔一段時間激活一次信號
- (void)testInterval {
    __block int num = 0;
    [[[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] takeUntil:self.rac_willDeallocSignal] subscribeNext:^(id x) {
        num++;
        NSLog(@"count:%d", num);
    }];
}

輸出結(jié)果為:
2017-06-30 14:57:02.553 RACDemo[77081:7365782] count:1
2017-06-30 14:57:03.553 RACDemo[77081:7365782] count:2
2017-06-30 14:57:04.553 RACDemo[77081:7365782] count:3
2017-06-30 14:57:05.553 RACDemo[77081:7365782] count:4
2017-06-30 14:57:06.553 RACDemo[77081:7365782] count:5
···

  • throttle 節(jié)流。一定時間內(nèi)不接收任何信號內(nèi)容,一旦到達(dá)這個時間就獲取最新最近的信號內(nèi)容。
- (void)testThrottle {
    [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"旅客A"];
        // 1秒時發(fā)送一條信息
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [subscriber sendNext:@"旅客B"];
        });
        // 1.5秒時發(fā)送一條信息
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [subscriber sendNext:@"插隊旅客"];
        });
        // 2秒時發(fā)送三條信息
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [subscriber sendNext:@"旅客C"];
            [subscriber sendNext:@"旅客D"];
            [subscriber sendNext:@"旅客E"];
        });
        // 3秒時發(fā)送一條信息
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [subscriber sendNext:@"旅客F"];
        });
        return nil;
    }] throttle:1] subscribeNext:^(id x) {
        NSLog(@"%@通過了",x);
    }];
}

輸出結(jié)果為:
2017-06-30 15:10:44.948 RACDemo[77224:7375779] 旅客A通過了
2017-06-30 15:10:46.947 RACDemo[77224:7375779] 旅客E通過了
2017-06-30 15:10:48.046 RACDemo[77224:7375779] 旅客F通過了

第五:MVVM + ReactiveCocoa

實戰(zhàn)開發(fā)中,ReactiveCocoa通常和MVVN設(shè)計模式結(jié)合使用,最快讓你上手ReactiveCocoa之進(jìn)階篇 后半部分已經(jīng)對其做了示例介紹,就不再贅述。

希望本文章可以作為代碼及原理查詢手冊,方便開發(fā)使用。
最后,貼出兩張速查表(第二張圖在簡書上放大也看不清,建議下載后放大查看),以示敬意~

Signal森林.png
ReactiveCocoa家族.png

參考鏈接:

最快讓你上手ReactiveCocoa之基礎(chǔ)篇
ReactiveCocoa v2.5 源碼解析之架構(gòu)總覽
ReactiveCocoa基本組件:理解和使用RACCommand
細(xì)說 ReactiveCocoa 的冷信號與熱信號(三):怎么處理冷信號與熱信號
ReactiveCocoa源碼閱讀之RACScheduler
RAC 中的雙向數(shù)據(jù)綁定 RACChannel
iOS ReactiveCocoa 最全常用API整理(可做為手冊查詢)
ReactiveCocoa 中 RACScheduler是如何封裝GCD的
RAC核心元素與信號流
這樣好用的ReactiveCocoa,根本停不下來
ReactiveCocoa操作方法(過濾,秩序,時間,重復(fù))
最快讓你上手ReactiveCocoa之進(jìn)階篇
RAC中combineLatest,zip,sample的測試
RACSignal實踐

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容