ReactiveCocoa 的一些高級(jí)用法

一. ReactiveCocoa常見操作介紹

1. ReactiveCocoa操作須知

  • 所有的信號(hào)RACSignal都可以進(jìn)行操作處理,因?yàn)樗胁僮鞣椒ǘ级x在RACStream.h中,因此只要繼承RACStream就有了操作處理方法坪创。

2. ReactiveCocoa操作思想

  • 運(yùn)用的是Hook(鉤子)思想瞎嬉,Hook是一種用于改變API(應(yīng)用程序編程接口:方法)執(zhí)行結(jié)果的技術(shù).
  • Hook用處:截獲API調(diào)用的技術(shù)。
  • Hook原理:在每次調(diào)用一個(gè)API返回結(jié)果之前扰肌,先執(zhí)行你自己的方法抛寝,改變結(jié)果的輸出

二. 高級(jí)操作

1. ReactiveCocoa核心方法bind

  • ReactiveCocoa操作的核心方法是bind(綁定),而且RAC中核心開發(fā)方式,也是綁定曙旭,之前的開發(fā)方式是賦值盗舰,而用RAC開發(fā),應(yīng)該把重心放在綁定桂躏,也就是可以在創(chuàng)建一個(gè)對(duì)象的時(shí)候钻趋,就綁定好以后想要做的事情,而不是等賦值之后在去做事情剂习。
  • 在開發(fā)中很少使用bind方法蛮位,bind屬于RAC中的底層方法较沪,RAC已經(jīng)封裝了很多好用的其他方法,底層都是調(diào)用bind失仁,用法比bind簡(jiǎn)單.
  • bind方法簡(jiǎn)單介紹和使用
    • 需求: 監(jiān)聽文本框的內(nèi)容, 每次輸出的時(shí)候, 在內(nèi)容后面品尚字符串"jun", 并顯示在label

方式一: 在返回結(jié)果后, 拼接字符串

    @weakify(self)
    [_textField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
        @strongify(self)
        self.showLabel.text = [NSString stringWithFormat:@"%@+%@", x, @"jun"];
    }];

方式二: 在返回結(jié)果前, 拼接字符串, 用bind方法操作

    [[_textField.rac_textSignal bind:^RACSignalBindBlock _Nonnull{
        return ^RACSignal *(id value, BOOL *stop){
            return [RACReturnSignal return:[NSString stringWithFormat:@"輸出: %@", value]];
        };
    }] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
  • bind方法介紹
    • bind方法參數(shù):需要傳入一個(gè)返回值是RACStreamBindBlockblock參數(shù)
    • RACStreamBindBlock是一個(gè)block的類型尸曼,返回值是信號(hào),參數(shù)(value,stop)萄焦,因此參數(shù)的block返回值也是一個(gè)block
    • 如下:
typedef RACSignal * _Nullable (^RACSignalBindBlock)(ValueType _Nullable value, BOOL *stop);

  • RACStreamBindBlock:
    • 參數(shù)一(value): 表示接收到信號(hào)的原始值控轿,還沒做處理
    • 參數(shù)二*stop: 用來控制綁定Block,如果*stop = yes,那么就會(huì)結(jié)束綁定拂封。
    • 返回值:信號(hào)茬射,做好處理,在通過這個(gè)信號(hào)返回出去冒签,一般使用RACReturnSignal,需要手動(dòng)導(dǎo)入頭文件RACReturnSignal.h
@interface RACReturnSignal<__covariant ValueType> : RACSignal<ValueType>

+ (RACSignal<ValueType> *)return:(ValueType)value;

@end
  • bind方法使用步驟:
    • 傳入一個(gè)返回值RACStreamBindBlockblock
    • 描述一個(gè)RACStreamBindBlock類型的bindBlock作為block的返回值在抛。
    • 描述一個(gè)返回結(jié)果的信號(hào),作為bindBlock的返回值镣衡。
    • 注意:在bindBlock中做信號(hào)結(jié)果的處理
  • bind底層實(shí)現(xiàn):
    • 源信號(hào)調(diào)用bind,會(huì)重新創(chuàng)建一個(gè)綁定信號(hào)霜定。
    • 當(dāng)綁定信號(hào)被訂閱,就會(huì)調(diào)用綁定信號(hào)中的didSubscribe廊鸥,生成一個(gè)bindingBlock望浩。
    • 當(dāng)源信號(hào)有內(nèi)容發(fā)出,就會(huì)把內(nèi)容傳遞到bindingBlock處理惰说,調(diào)用bindingBlock(value,stop)
    • 調(diào)用bindingBlock(value,stop)磨德,會(huì)返回一個(gè)內(nèi)容處理完成的信號(hào)(RACReturnSignal)
    • 訂閱RACReturnSignal吆视,就會(huì)拿到綁定信號(hào)的訂閱者典挑,把處理完成的信號(hào)內(nèi)容發(fā)送出來

2. 映射(flattenMap,Map)

  • flattenMapMap用于把源信號(hào)內(nèi)容映射成新的內(nèi)容
  • Swift中系統(tǒng)API就已經(jīng)有了這些函數(shù)的用法, 詳情可參考我的這篇文章Swift函數(shù)式編程之Map&Reduce&Filter

2-1. flattenMap

把源信號(hào)的內(nèi)容映射成一個(gè)新的信號(hào)啦吧,信號(hào)可以是任意類型

  • flattenMap使用步驟:
    • 傳入一個(gè)block您觉,block類型是返回值RACStream,參數(shù)value
    • 參數(shù)value就是源信號(hào)的內(nèi)容授滓,拿到源信號(hào)的內(nèi)容做處理
    • 包裝成RACReturnSignal信號(hào)琳水,返回出去
  • flattenMap底層實(shí)現(xiàn):
    • 0.flattenMap內(nèi)部調(diào)用bind方法實(shí)現(xiàn)的,flattenMapblock的返回值,會(huì)作為bindbindBlock的返回值般堆。
    • 1.當(dāng)訂閱綁定信號(hào)在孝,就會(huì)生成bindBlock
    • 2.當(dāng)源信號(hào)發(fā)送內(nèi)容淮摔,就會(huì)調(diào)用bindBlock(value, *stop)
    • 3.調(diào)用bindBlock私沮,內(nèi)部就會(huì)調(diào)用flattenMapblockflattenMapblock作用:就是把處理好的數(shù)據(jù)包裝成信號(hào)
    • 4.返回的信號(hào)最終會(huì)作為bindBlock中的返回信號(hào)和橙,當(dāng)做bindBlock的返回信號(hào)仔燕。
    • 5.訂閱bindBlock的返回信號(hào)造垛,就會(huì)拿到綁定信號(hào)的訂閱者,把處理完成的信號(hào)內(nèi)容發(fā)送出來
- (__kindof RACStream *)flattenMap:(__kindof RACStream * (^)(id value))block {
    Class class = self.class;

    return [[self bind:^{
        return ^(id value, BOOL *stop) {
            id stream = block(value) ?: [class empty];
            NSCAssert([stream isKindOfClass:RACStream.class], @"Value returned from -flattenMap: is not a stream: %@", stream);

            return stream;
        };
    }] setNameWithFormat:@"[%@] -flattenMap:", self.name];
}

簡(jiǎn)單使用

    @weakify(self)
    [[_textField.rac_textSignal flattenMap:^__kindof RACSignal * _Nullable(NSString * _Nullable value) {
        //源信號(hào)發(fā)出的時(shí)候涨享,就會(huì)調(diào)用這個(gè)block筋搏。
        // 返回值:綁定信號(hào)的內(nèi)容.
        return [RACReturnSignal return:[NSString stringWithFormat:@"flat輸出: %@", value]];
    }] subscribeNext:^(id  _Nullable x) {
        @strongify(self)
        //訂閱綁定信號(hào), 每當(dāng)原信號(hào)發(fā)送內(nèi)容, 處理后, 就會(huì)調(diào)用這個(gè)black
        self.showLabel.text = x;
        NSLog(@"%@", x);
    }];

2-2. Map

Map作用:把源信號(hào)的值映射成一個(gè)新的值

  • Map使用步驟:
    • 傳入一個(gè)block,類型是返回對(duì)象,參數(shù)是value
    • value就是源信號(hào)的內(nèi)容厕隧,直接拿到源信號(hào)的內(nèi)容做處理
    • 把處理好的內(nèi)容奔脐,直接返回就好了,不用包裝成信號(hào)吁讨,返回的值髓迎,就是映射的值。
  • Map底層實(shí)現(xiàn):
    • Map底層其實(shí)是調(diào)用flatternMap, Map中block中的返回的值會(huì)作為flatternMap中block中的值建丧。
    • 當(dāng)訂閱綁定信號(hào)排龄,就會(huì)生成bindBlock
    • 當(dāng)源信號(hào)發(fā)送內(nèi)容翎朱,就會(huì)調(diào)用bindBlock(value, *stop)
    • 調(diào)用bindBlock橄维,內(nèi)部就會(huì)調(diào)用flattenMapblock
    • flattenMapblock內(nèi)部會(huì)調(diào)用Map中的block,把Map中的block返回的內(nèi)容包裝成返回的信號(hào)拴曲。
    • 返回的信號(hào)最終會(huì)作為bindBlock中的返回信號(hào)争舞,當(dāng)做bindBlock的返回信號(hào)。
    • 訂閱bindBlock的返回信號(hào)澈灼,就會(huì)拿到綁定信號(hào)的訂閱者竞川,把處理完成的信號(hào)內(nèi)容發(fā)送出來
- (__kindof RACStream *)map:(id (^)(id value))block {
    NSCParameterAssert(block != nil);

    Class class = self.class;
    
    return [[self flattenMap:^(id value) {
        return [class return:block(value)];
    }] setNameWithFormat:@"[%@] -map:", self.name];
}

簡(jiǎn)單使用

    //Map
    [[_textField.rac_textSignal map:^id _Nullable(NSString * _Nullable value) {
        return [NSString stringWithFormat:@"map輸出: %@", value];
    }] subscribeNext:^(id  _Nullable x) {
        @strongify(self)
        self.showLabel.text = x;
        NSLog(@"%@", x);
    }];
    
    //對(duì)數(shù)組的處理
    NSArray *arr = @[@"2", @"3", @"a", @"g"];
    RACSequence *sequence = [arr.rac_sequence map:^id _Nullable(id  _Nullable value) {
        return [NSString stringWithFormat:@"-%@-", value];
    }];
    
    NSLog(@"%@", [sequence array]);
    
    /*輸出: 
    2018-03-24 14:13:32.421337+0800 ReactiveObjc[9043:492929] (
        "-2-",
        "-3-",
        "-a-",
        "-g-"
    )
    */

2-3. FlatternMapMap的區(qū)別

  • FlatternMap中的Block返回信號(hào)。
  • Map中的Block返回對(duì)象叁熔。
  • 開發(fā)中委乌,如果信號(hào)發(fā)出的值不是信號(hào),映射一般使用Map
  • 開發(fā)中荣回,如果信號(hào)發(fā)出的值是信號(hào)遭贸,映射一般使用FlatternMap
  • 信號(hào)中信號(hào)
    • 當(dāng)一個(gè)信號(hào)需要返回另一個(gè)信號(hào)中的值的時(shí)候
    • 讓我們來看看下面這個(gè)例子
#pragma 信號(hào)中信號(hào)
- (void)singleAndSingle {
    //創(chuàng)建信號(hào)中信號(hào)
    RACSubject *sonSingle = [RACSubject subject];
    RACSubject *single = [RACSubject subject];
    
    [[sonSingle flattenMap:^__kindof RACSignal * _Nullable(id  _Nullable value) {
        //sonSingle發(fā)送信號(hào)時(shí), 才會(huì)調(diào)用
        return value;
    }] subscribeNext:^(id  _Nullable x) {
        //只有sonSingle的子信號(hào), 大宋消息時(shí), 才會(huì)調(diào)用
        NSLog(@"輸出: %@", x);
    }];
    
    //信號(hào)中信號(hào)發(fā)送子信號(hào)
    [sonSingle sendNext:single];
    //子信號(hào)發(fā)送內(nèi)容
    [single sendNext:@123];
}

3. 組合

3-1. concat

按照某一固定順序拼接信號(hào),當(dāng)多個(gè)信號(hào)發(fā)出的時(shí)候心软,有順序的接收信號(hào)

//讓我們先看一下一般的正常操作
- (void)setConcatAction {
    //當(dāng)需要按順序執(zhí)行的時(shí)候: 先執(zhí)行A, 在執(zhí)行B
    RACSubject *subjectA = [RACSubject subject];
    RACSubject *subjectB = [RACReplaySubject subject];
    
    NSMutableArray *array = [NSMutableArray array];
    
    //訂閱信號(hào)
    [subjectA subscribeNext:^(id  _Nullable x) {
        [array addObject:x];
    }];
    [subjectB subscribeNext:^(id  _Nullable x) {
        [array addObject:x];
    }];
    
    //發(fā)送信號(hào)
    [subjectB sendNext:@"B"];
    [subjectA sendNext:@"A"];
    [subjectA sendCompleted];
    
    //輸出: [B, A]
    NSLog(@"%@", array);
}
  • 很明顯, 上述的結(jié)果并未達(dá)到我們的需求: 限制性A, 在執(zhí)行B
  • 下面我們看看使用concat后的執(zhí)行情況
- (void)setConcatAction {
    //當(dāng)需要按順序執(zhí)行的時(shí)候: 先執(zhí)行A, 在執(zhí)行B
    RACSubject *subC = [RACSubject subject];
    RACSubject *subD = [RACReplaySubject subject];
    
    NSMutableArray *array2 = [NSMutableArray array];
    
    //訂閱信號(hào)
    [[subC concat:subD] subscribeNext:^(id  _Nullable x) {
        [array2 addObject:x];
    }];
    
    //發(fā)送信號(hào)
    [subD sendNext:@"D"];
    [subC sendNext:@"C"];
    [subC sendCompleted];
    
    //輸出: [C, D]
    NSLog(@"%@", array2);
}
  • 可以看到, 輸出的結(jié)果和我們預(yù)想的一樣, 順序輸出
  • 那么, concat的底層到底是如何實(shí)現(xiàn)的呢?
- (RACSignal *)concat:(RACSignal *)signal {
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        RACCompoundDisposable *compoundDisposable = [[RACCompoundDisposable alloc] init];

        RACDisposable *sourceDisposable = [self subscribeNext:^(id x) {
            [subscriber sendNext:x];
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            RACDisposable *concattedDisposable = [signal subscribe:subscriber];
            [compoundDisposable addDisposable:concattedDisposable];
        }];

        [compoundDisposable addDisposable:sourceDisposable];
        return compoundDisposable;
    }] setNameWithFormat:@"[%@] -concat: %@", self.name, signal];
}
  • concat底層實(shí)現(xiàn):
    • 當(dāng)拼接信號(hào)被訂閱革砸,就會(huì)調(diào)用拼接信號(hào)的didSubscribe
    • didSubscribe中,會(huì)先訂閱第一個(gè)源信號(hào)subjectA
    • 會(huì)執(zhí)行第一個(gè)源信號(hào)subjectAdidSubscribe
    • 第一個(gè)源信號(hào)subjectAdidSubscribe中發(fā)送值糯累,就會(huì)調(diào)用第一個(gè)源信號(hào)subjectA訂閱者的nextBlock, 通過拼接信號(hào)的訂閱者把值發(fā)送出來.
    • 第一個(gè)源信號(hào)subjectAdidSubscribe中發(fā)送完成,就會(huì)調(diào)用第一個(gè)源信號(hào)subjectA訂閱者的completedBlock,訂閱第二個(gè)源信號(hào)subjectB這時(shí)候才激活subjectB
    • 訂閱第二個(gè)源信號(hào)subjectB,執(zhí)行第二個(gè)源信subjectB號(hào)的didSubscribe
    • 第二個(gè)源信號(hào)subjectBdidSubscribe中發(fā)送值,就會(huì)通過拼接信號(hào)的訂閱者把值發(fā)送出來.

3-2. then

用于連接兩個(gè)信號(hào)册踩,當(dāng)?shù)谝粋€(gè)信號(hào)完成泳姐,才會(huì)連接then返回的信號(hào)

- (RACSignal *)then:(RACSignal * (^)(void))block {
    NSCParameterAssert(block != nil);

    return [[[self
        ignoreValues]
        concat:[RACSignal defer:block]]
        setNameWithFormat:@"[%@] -then:", self.name];
}

//ignoreValues底層實(shí)現(xiàn)
- (RACSignal *)ignoreValues {
    return [[self filter:^(id _) {
        return NO;
    }] setNameWithFormat:@"[%@] -ignoreValues", self.name];
}
  • 實(shí)現(xiàn)原理
    • 底層會(huì)調(diào)用filter過濾掉本身信號(hào)發(fā)出的值(filter后面會(huì)講到)
    • 然后再使用concat連接then返回的信號(hào)
    • 下面是測(cè)試用例
- (void)setThenAction {
    RACSubject *subjectA = [RACReplaySubject subject];
    RACSubject *subjectB = [RACReplaySubject subject];
    
    //發(fā)送信號(hào)
    [subjectA sendNext:@"A"];
    [subjectA sendCompleted];
    [subjectB sendNext:@"B"];
    
    //訂閱信號(hào)
    [[subjectA then:^RACSignal * _Nonnull{
        return subjectB;
    }] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    
    //這里只會(huì)輸出: B
    //不會(huì)輸出: A
}

3-3. merge

把多個(gè)信號(hào)合并為一個(gè)信號(hào),任何一個(gè)信號(hào)有新值的時(shí)候就會(huì)調(diào)用

- (RACSignal *)merge:(RACSignal *)signal {
    return [[RACSignal
        merge:@[ self, signal ]]
        setNameWithFormat:@"[%@] -merge: %@", self.name, signal];
}

+ (RACSignal *)merge:(id<NSFastEnumeration>)signals {
    NSMutableArray *copiedSignals = [[NSMutableArray alloc] init];
    for (RACSignal *signal in signals) {
        [copiedSignals addObject:signal];
    }

    return [[[RACSignal
        createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
            for (RACSignal *signal in copiedSignals) {
                [subscriber sendNext:signal];
            }

            [subscriber sendCompleted];
            return nil;
        }]
        flatten]
        setNameWithFormat:@"+merge: %@", copiedSignals];
}
  • 底層實(shí)現(xiàn)
    • 1.合并信號(hào)被訂閱的時(shí)候暂吉,就會(huì)遍歷所有信號(hào)胖秒,并且發(fā)出這些信號(hào)缎患。
    • 2.每發(fā)出一個(gè)信號(hào),這個(gè)信號(hào)就會(huì)被訂閱
    • 3.也就是合并信號(hào)一被訂閱阎肝,就會(huì)訂閱里面所有的信號(hào)挤渔。
    • 4.只要有一個(gè)信號(hào)被發(fā)出就會(huì)被監(jiān)聽。
- (void)setMergeAction {
    // 只要想無序的整合信號(hào)數(shù)據(jù)
    RACSubject *subjectA = [RACSubject subject];
    RACSubject *subjectB = [RACSubject subject];
    RACSubject *subjectC = [RACSubject subject];

    //合并信號(hào)
    RACSignal *single = [[subjectA merge:subjectB] merge:subjectC];
    
    //訂閱信號(hào)
    [single subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    
    //發(fā)出消息
    [subjectA sendNext:@"A"];
    [subjectC sendNext:@"C"];
    [subjectB sendNext:@"B"];
}

//輸出結(jié)果(分別輸出): A, C, B

3-4. zipWith

把兩個(gè)信號(hào)壓縮成一個(gè)信號(hào)风题,只有當(dāng)兩個(gè)信號(hào)同時(shí)發(fā)出信號(hào)內(nèi)容時(shí)判导,并且把兩個(gè)信號(hào)的內(nèi)容合并成一個(gè)元組,才會(huì)觸發(fā)壓縮流的next事件

  • 底層實(shí)現(xiàn):
    • 1.定義壓縮信號(hào)沛硅,內(nèi)部就會(huì)自動(dòng)訂閱subjectA眼刃,subjectB
    • 2.每當(dāng)subjectA或者subjectB發(fā)出信號(hào),就會(huì)判斷subjectA摇肌,subjectB有沒有發(fā)出個(gè)信號(hào)擂红,有就會(huì)把最近發(fā)出的信號(hào)都包裝成元組發(fā)出。
- (void)setZipwithAction {
    // 只要想無序的整合信號(hào)數(shù)據(jù)
    RACSubject *subjectA = [RACSubject subject];
    RACSubject *subjectB = [RACSubject subject];
    
    //合并信號(hào)
    RACSignal *single = [subjectA zipWith:subjectB];
    
    //訂閱信號(hào)
    [single subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    
    //發(fā)出消息
    [subjectA sendNext:@"A"];
    [subjectB sendNext:@"B"];
    
    /* 輸出:
     (A, B)
     */
}

3-5. combineLatest

  • 將多個(gè)信號(hào)合并起來围小,并且拿到各個(gè)信號(hào)的最新的值
  • 必須每個(gè)合并的信號(hào)至少都有過一次sendNext昵骤,才會(huì)觸發(fā)合并的信號(hào)
  • 這里我們考慮這樣一個(gè)需求: 在登錄頁(yè)面, 只有在賬號(hào)密碼都輸入的情況下, 登錄按鈕才可點(diǎn)擊, 否則不可點(diǎn)擊
  • 正常情況下我們需要監(jiān)聽每一個(gè)文本框的輸入
  • 下面我們來看一下combineLatest控制登錄按鈕是否可點(diǎn)擊
- (void)setCombineLatest {
    //把兩個(gè)信號(hào)組合成一個(gè)信號(hào),跟zip一樣,沒什么區(qū)別
    RACSignal *single = [_accountText.rac_textSignal combineLatestWith:_passwordText.rac_textSignal];
    
    [single subscribeNext:^(id  _Nullable x) {
        RACTupleUnpack(NSString *account, NSString *password) = x;
        
        _loginButton.enabled = account.length > 0 && password.length > 0;
    }];
}
  • 底層實(shí)現(xiàn):
    • 1.當(dāng)組合信號(hào)被訂閱肯适,內(nèi)部會(huì)自動(dòng)訂閱兩個(gè)信號(hào),必須兩個(gè)信號(hào)都發(fā)出內(nèi)容变秦,才會(huì)被觸發(fā)。(而zip, 是兩個(gè)信號(hào)同事發(fā)出內(nèi)容, 才會(huì)觸發(fā))
    • 2.把兩個(gè)信號(hào)組合成元組發(fā)出疹娶。

3-6. reduce

聚合:用于信號(hào)發(fā)出是元組的內(nèi)容伴栓,把信號(hào)發(fā)出元組的值聚合成一個(gè)值

這里我們把上面的代碼, 使用RACSingle的一個(gè)類方法優(yōu)化一下

- (void)setReduceAction {
    // reduce:把多個(gè)信號(hào)的值,聚合為一個(gè)值
    RACSignal *single = [RACSignal combineLatest:@[_accountText.rac_textSignal, _passwordText.rac_textSignal] reduce:^id (NSString *account, NSString *password){
        return @(account.length > 0 && password.length > 0);
    }];
    
    [single subscribeNext:^(id  _Nullable x) {
        _loginButton.enabled = [x boolValue];
    }];
}
  • RACSingle類方法
    • 參數(shù)一: (id<NSFastEnumeration>)類型
      • NSFastEnumeration我們?cè)谏弦黄恼?a target="_blank" rel="nofollow">ReactiveCocoa之集合使用詳解02中簡(jiǎn)單介紹過
      • NSFastEnumeration: 是一個(gè)協(xié)議, 所有遵循該協(xié)議的類, 均可視為一個(gè)數(shù)組, 例如NSArray
      • 故這里, 應(yīng)該傳一個(gè)包含RACSingle信號(hào)的數(shù)組
    • 參數(shù)二: (RACGenericReduceBlock)reduceBlock是一個(gè)black
typedef ValueType _Nonnull (^RACGenericReduceBlock)();

//reduceblcok中的參數(shù),有多少信號(hào)組合雨饺,reduceblcok就有多少參數(shù)钳垮,每個(gè)參數(shù)就是之前信號(hào)發(fā)出的內(nèi)容

這里用一個(gè)宏, 急需將上面的代碼簡(jiǎn)化一下

- (void)setReduceAction {

    RAC(_loginButton, enabled) = [RACSignal combineLatest:@[_accountText.rac_textSignal, _passwordText.rac_textSignal] reduce:^id (NSString *account, NSString *password){
        return @(account.length > 0 && password.length > 0);
    }];
}

上面用到了一個(gè)宏RAC, 這里暫不贅述, 以后會(huì)集中整理一下 RAC中的宏, 具體實(shí)現(xiàn)如下

#define RAC(TARGET, ...) \
    metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
        (RAC_(TARGET, __VA_ARGS__, nil)) \
        (RAC_(TARGET, __VA_ARGS__))

/// Do not use this directly. Use the RAC macro above.
#define RAC_(TARGET, KEYPATH, NILVALUE) \
    [[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:(TARGET) nilValue:(NILVALUE)][@keypath(TARGET, KEYPATH)]

4. 過濾

4-1. filter

過濾信號(hào), 過濾掉不符合條件的信號(hào)

- (void) filterAction{
    //filter
    //截取等于11位的字符
    [[_accountText.rac_textSignal filter:^BOOL(NSString * _Nullable value) {
        //類似手機(jī)號(hào)的輸入, 只有等于11位的時(shí)候才返回true
        return value.length == 11;
    }]subscribeNext:^(NSString * _Nullable x) {
        //這里只會(huì)返回等于11位的字符
        NSLog(@"filter = %@", x);
    }];
}

filter底層是調(diào)用的flatMap方法, 如下:

- (__kindof RACStream *)filter:(BOOL (^)(id value))block {
    NSCParameterAssert(block != nil);

    Class class = self.class;
    
    return [[self flattenMap:^ id (id value) {
        if (block(value)) {
            return [class return:value];
        } else {
            return class.empty;
        }
    }] setNameWithFormat:@"[%@] -filter:", self.name];
}

4-2. ignore

忽略掉某些特定值的信號(hào)

- (void)setIgnoreAction {
    ///ignore
    //這里的測(cè)試只有第一個(gè)字符位: m的時(shí)候能看到效果
    [[_accountText.rac_textSignal ignore:@"m"] subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"ignore = %@", x);
    }];
    
    //ignoreValues: 忽略所有信號(hào)
    [[_passwordText.rac_textSignal ignoreValues] subscribeNext:^(id  _Nullable x) {
        NSLog(@"allIgnore = %@", x);
    }];
}

ignore方法的底層都是調(diào)用的filter方法

//ignore
- (__kindof RACStream *)ignore:(id)value {
    return [[self filter:^ BOOL (id innerValue) {
        return innerValue != value && ![innerValue isEqual:value];
    }] setNameWithFormat:@"[%@] -ignore: %@", self.name, RACDescription(value)];
}

//ignoreValues
- (RACSignal *)ignoreValues {
    return [[self filter:^(id _) {
        return NO;
    }] setNameWithFormat:@"[%@] -ignoreValues", self.name];
}

4-3. distinctUntilChanged

  • 當(dāng)上一次的值和當(dāng)前的值有明顯的變化就會(huì)發(fā)出信號(hào),否則會(huì)被忽略掉额港。
  • 在開發(fā)中饺窿,刷新UI經(jīng)常使用,只有兩次數(shù)據(jù)不一樣才需要刷新
//distinctUntilChanged
- (void)setdistinctUntilChanged {
    //創(chuàng)建信號(hào)
    RACSubject *subject = [RACSubject subject];

    //訂閱
    [[subject distinctUntilChanged] subscribeNext:^(id  _Nullable x) {
        NSLog(@"distinctUntilChanged = %@", x);
    }];
    
    [subject sendNext:@12];
    [subject sendNext:@12];
    [subject sendNext:@23];
    
    /*輸出結(jié)果:只會(huì)輸出兩次
     distinctUntilChanged = 12
     distinctUntilChanged = 23
     */
}

distinctUntilChanged底層是調(diào)用的bind高級(jí)用法

- (__kindof RACStream *)distinctUntilChanged {
    Class class = self.class;

    return [[self bind:^{
        __block id lastValue = nil;
        __block BOOL initial = YES;

        return ^(id x, BOOL *stop) {
            if (!initial && (lastValue == x || [x isEqual:lastValue])) return [class empty];

            initial = NO;
            lastValue = x;
            return [class return:x];
        };
    }] setNameWithFormat:@"[%@] -distinctUntilChanged", self.name];
}

4-4. take

從開始一共取N次的信號(hào), 當(dāng)遇到sendCompleted語句執(zhí)行時(shí), 會(huì)提前停止發(fā)送信號(hào)

- (void)setTakeAndTakeLast {
    //take
    RACSubject *subject1 = [RACSubject subject];
    [[subject1 take:2] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    
    [subject1 sendNext:@1];
    [subject1 sendNext:@2];
    [subject1 sendCompleted];
    [subject1 sendNext:@3];
    
    //分別輸出: 1, 2
}

//如果上面發(fā)送信號(hào)的代碼調(diào)整為
    [subject1 sendNext:@1];
    [subject1 sendCompleted];
    [subject1 sendNext:@2];
    [subject1 sendNext:@3];
    
    //那么輸出結(jié)果將會(huì),只輸出: 1

4-5. takeLast

取調(diào)用sendCompleted之前的N次信號(hào),前提條件移斩,訂閱者必須調(diào)用sendCompleted肚医,否則不會(huì)執(zhí)行任何操作

- (void)setTakeAndTakeLast {
    //takeLast
    RACSubject *subject1 = [RACSubject subject];
    [[subject1 takeLast:2] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    
    [subject1 sendNext:@1];
    [subject1 sendNext:@2];
    [subject1 sendNext:@3];
    [subject1 sendCompleted];
}

4-6. takeUntil

只要傳入的信號(hào)發(fā)送完成或者subject2開始發(fā)送信號(hào)的時(shí)候,就不會(huì)再接收信號(hào)的內(nèi)容

- (void)setTakeAndTakeLast {
    //takeUntil
    RACSubject *subject1 = [RACSubject subject];
    RACSubject *subject2 = [RACSubject subject];
    
    [[subject1 takeUntil:subject2] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    
    [subject1 sendNext:@11];
    [subject1 sendNext:@12];
//    [subject1 sendCompleted];
    [subject1 sendNext:@13];
    [subject2 sendNext:@"21"];
    [subject2 sendNext:@"22"];
    
    //這樣會(huì)輸出: 11, 12, 13
    //當(dāng)sendCompleted取消注釋的時(shí)候, 只會(huì)輸出: 11, 12
}

4-7. switchToLatest

  • 主要用于信號(hào)的信號(hào), 有時(shí)候也會(huì)發(fā)出信號(hào), 會(huì)在信號(hào)的信號(hào)中獲取其發(fā)送的最新的信號(hào)
  • 方法的底層是調(diào)用了flattenMap方法
- (RACSignal *)switchToLatest {
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        RACMulticastConnection *connection = [self publish];

        RACDisposable *subscriptionDisposable = [[connection.signal
            flattenMap:^(RACSignal *x) {
                NSCAssert(x == nil || [x isKindOfClass:RACSignal.class], @"-switchToLatest requires that the source signal (%@) send signals. Instead we got: %@", self, x);

                // -concat:[RACSignal never] prevents completion of the receiver from
                // prematurely terminating the inner signal.
                return [x takeUntil:[connection.signal concat:[RACSignal never]]];
            }]
            subscribe:subscriber];

        RACDisposable *connectionDisposable = [connection connect];
        return [RACDisposable disposableWithBlock:^{
            [subscriptionDisposable dispose];
            [connectionDisposable dispose];
        }];
    }] setNameWithFormat:@"[%@] -switchToLatest", self.name];
}

下面我們看一下具體的使用示例

- (void)setswitchToLatest {
    //信號(hào)的信號(hào)
    RACSubject *subject1 = [RACSubject subject];
    RACSubject *subject2 = [RACSubject subject];

    //獲取信號(hào)中信號(hào)最近發(fā)出信號(hào),訂閱最近發(fā)出的信號(hào)
    [[subject1 switchToLatest] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    
    //發(fā)送信號(hào)
    [subject1 sendNext:subject2];
    [subject2 sendNext:@"信號(hào)中信號(hào)"];
    
    //最終結(jié)果輸出: "信號(hào)中信號(hào)"
}

4-8. skip

跳過N個(gè)信號(hào)后, 再開始訂閱信號(hào)

- (void)setSkipAction {
    //創(chuàng)建信號(hào)
    RACSubject *subject = [RACSubject subject];
    
    //訂閱信號(hào)
    //要求跳過2個(gè)信號(hào)
    [[subject skip:2] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    
    //發(fā)送信號(hào)
    [subject sendNext:@1];
    [subject sendNext:@2];
    [subject sendNext:@3];
    [subject sendNext:@4];
    
    //因?yàn)樯厦嫣^了兩個(gè)信號(hào), 所以這里只會(huì)輸出: 3, 4
}

5. 定時(shí)操作

5-1. interval

定時(shí)器, 每隔一段時(shí)間發(fā)出信號(hào)

    //RAC定時(shí)器, 每隔一段時(shí)間執(zhí)行一次
    [[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSDate * _Nullable x) {
        NSLog(@"定時(shí)器");
    }];

其中RACSchedulerRAC中管理線程的類

5-2. delay

延遲一段時(shí)間都發(fā)送信號(hào)

    //delay: 延遲執(zhí)行
    [[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        [subscriber sendNext:@"delay"];
        return nil;
    }] delay:2] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];

5-3. timeout

超時(shí), 可以讓一個(gè)信號(hào)在一定時(shí)間后自動(dòng)報(bào)錯(cuò)

    //timeout: 超時(shí), 可以讓一個(gè)信號(hào)在一定時(shí)間后自動(dòng)報(bào)錯(cuò)
    RACSignal *single = [[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        return nil;
    }] timeout:2 onScheduler:[RACScheduler currentScheduler]];
    
    [single subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    } error:^(NSError * _Nullable error) {
        //2秒后自動(dòng)調(diào)用
        NSLog(@"%@", error);
    }];

6. 重復(fù)操作

6-1. retry

重試 :只要失敗向瓷,就會(huì)重新執(zhí)行創(chuàng)建信號(hào)中的block,直到成功.

- (void)setResertAction {
    //retry
    __block int i = 0;
    [[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        if (i == 5) {
            [subscriber sendNext:@12];
        } else {
            NSLog(@"發(fā)生錯(cuò)誤");
            [subscriber sendError:nil];
        }
        i++;
        
        return nil;
    }] retry] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    } error:^(NSError * _Nullable error) {
        NSLog(@"%@", error);
    }];
    
    
    /*輸出結(jié)果
     2018-03-30 15:44:08.412860+0800 ReactiveObjc[4125:341376] 發(fā)生錯(cuò)誤
     2018-03-30 15:44:08.461105+0800 ReactiveObjc[4125:341376] 發(fā)生錯(cuò)誤
     2018-03-30 15:44:08.461897+0800 ReactiveObjc[4125:341376] 發(fā)生錯(cuò)誤
     2018-03-30 15:44:08.462478+0800 ReactiveObjc[4125:341376] 發(fā)生錯(cuò)誤
     2018-03-30 15:44:08.462913+0800 ReactiveObjc[4125:341376] 發(fā)生錯(cuò)誤
     2018-03-30 15:44:08.463351+0800 ReactiveObjc[4125:341376] 12
     */
}

6-2. replay

重放:當(dāng)一個(gè)信號(hào)被多次訂閱,反復(fù)播放內(nèi)容

    //replay
    RACSignal *single = [[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        [subscriber sendNext:@23];
        [subscriber sendNext:@34];
        
        return nil;
    }] replay];
    
    [single subscribeNext:^(id  _Nullable x) {
        NSLog(@"第一次訂閱-%@", x);
    }];
    
    [single subscribeNext:^(id  _Nullable x) {
        NSLog(@"第二次訂閱-%@", x);
    }];
    
    /*輸出結(jié)果:
     2018-03-30 15:51:20.115052+0800 ReactiveObjc[4269:361568] 第一次訂閱-23
     2018-03-30 15:51:20.115195+0800 ReactiveObjc[4269:361568] 第一次訂閱-34
     2018-03-30 15:51:20.115278+0800 ReactiveObjc[4269:361568] 第二次訂閱-23
     2018-03-30 15:51:20.115352+0800 ReactiveObjc[4269:361568] 第二次訂閱-34
     */

6-3. throttle

節(jié)流:當(dāng)某個(gè)信號(hào)發(fā)送比較頻繁時(shí)肠套,可以使用節(jié)流, 在一定時(shí)間(1秒)內(nèi),不接收任何信號(hào)內(nèi)容猖任,過了這個(gè)時(shí)間(1秒)獲取最后發(fā)送的信號(hào)內(nèi)容發(fā)出你稚。

    RACSubject *subject = [RACSubject subject];
    
    [[subject throttle:0.001] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    
    [subject sendNext:@10];
    [subject sendNext:@11];
    [subject sendNext:@12];
    [subject sendNext:@13];
    [subject sendNext:@14];
    [subject sendNext:@15];
    [subject sendNext:@16];
    [subject sendNext:@17];
    [subject sendNext:@18];
    
    //這里因?yàn)閳?zhí)行的速度非常快, 所以這里輸出的結(jié)果只有最后一個(gè): 18
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市刁赖,隨后出現(xiàn)的幾起案子搁痛,更是在濱河造成了極大的恐慌,老刑警劉巖宇弛,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸡典,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡枪芒,警方通過查閱死者的電腦和手機(jī)彻况,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來病苗,“玉大人疗垛,你說我怎么就攤上這事×螂” “怎么了贷腕?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)咬展。 經(jīng)常有香客問我泽裳,道長(zhǎng),這世上最難降的妖魔是什么破婆? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任涮总,我火速辦了婚禮,結(jié)果婚禮上祷舀,老公的妹妹穿的比我還像新娘瀑梗。我一直安慰自己,他們只是感情好裳扯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布抛丽。 她就那樣靜靜地躺著,像睡著了一般饰豺。 火紅的嫁衣襯著肌膚如雪亿鲜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天冤吨,我揣著相機(jī)與錄音蒿柳,去河邊找鬼。 笑死漩蟆,一個(gè)胖子當(dāng)著我的面吹牛垒探,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播怠李,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼叛复,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼仔引!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起褐奥,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎翘簇,沒想到半個(gè)月后撬码,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡版保,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年呜笑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彻犁。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡叫胁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出汞幢,到底是詐尸還是另有隱情驼鹅,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布森篷,位于F島的核電站输钩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏仲智。R本人自食惡果不足惜买乃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望钓辆。 院中可真熱鬧剪验,春花似錦、人聲如沸前联。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蛀恩。三九已至疫铜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間双谆,已是汗流浹背壳咕。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留顽馋,地道東北人谓厘。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像寸谜,于是被迫代替她去往敵國(guó)和親竟稳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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