ReactiveCocoa的一些特性細節(jié)

本文基于objc版本的ReactiveCocoa v2.5
ps:看這篇文章之前強烈推薦大家用5分鐘時間看一下我的上一篇文章毫無干貨的帶你理解什么是函數(shù)式編程


RACSequence

  • 有懶加載和緩存結(jié)果的功能,只會把f作用到value上一次。
  • 結(jié)果會在第一次使用到的時候再執(zhí)行block進行計算并緩存,再次使用時從緩存中取出,未用到的結(jié)果則不會計算。
- (void)sequence{
    NSArray *strings = @[ @"A", @"B", @"C" ];
    RACSequence *sequence = [strings.rac_sequence map:^(NSString *str) {
        NSLog(@"%@", str);
        return [str stringByAppendingString:@"_"];
    }];
    
    NSLog(@"1");
    NSString *concatA = sequence.head;//打印A
    NSLog(@"2");
    NSString *concatB = sequence.tail.head;//打印B
    NSLog(@"3");
    NSString *concatB2 = sequence.tail.head;//不打印,直接從緩存中取
    NSLog(@"4");
    RACSequence *derivedSequence = [sequence map:^(NSString *str) {
        NSLog(@"5");
        return [@"_" stringByAppendingString:str];
    }];
    NSLog(@"6");
    //tail.head的目的是訪問B,但是A和B都已經(jīng)緩存過了,所以仍然不會重復(fù)執(zhí)行sequence的block。而是打印兩次5
    NSString *concatB3 = derivedSequence.tail.head;
    NSLog(@"7");
}

輸出結(jié)果:1、A、2、B、3、4、6、5、5、7


RACEagerSequence

  • 不進行懶加載,立即將f作用到value上。
  • 需要導(dǎo)入私有類<ReactiveCocoa/RACEagerSequence.h>
- (void)eagerSequence{
    NSArray *strings = @[ @"A", @"B", @"C" ];
    RACSequence *sequence = [strings.rac_sequence map:^(NSString *str) {
        NSLog(@"%@", str);
        return [str stringByAppendingString:@"_"];
    }];
    
    RACEagerSequence *eagerSequence = [sequence eagerSequence];
    NSLog(eagerSequence.head);
    NSLog(eagerSequence.tail.head);
    NSLog(eagerSequence.head);
    NSLog(eagerSequence.tail.head);
} 

執(zhí)行到[sequence eagerSequence];
輸出:A、B、C。
之后輸出:A_、B_、A_、B_。


RACSignal

  • signal沒有RACSequence那樣的懶加載機制。
  • 每一次事件之間只會串行執(zhí)行,絕不會同事并發(fā)多個事件。
  • 所以-subscribeNext:error:completed:內(nèi)部無需做同步操作
  • subscription始終會在一個RACScheduler進行回調(diào)。
  • error具有異常的語義和try catch不同,他會立即將錯誤發(fā)向所有依賴它的signal并且將整個函數(shù)鏈終止。

subscribeNext

  • 每增加一個subscription,函數(shù)都會執(zhí)行一次,會反復(fù)產(chǎn)生副作用。
  • 如果signal的副作用出了問題則很難控制和調(diào)試,使用時需要謹慎。
    __block int aNumber = 0;
    
    // 每增加一個subscription。createSignal的block機會被執(zhí)行一次。
    RACSignal *aSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
        aNumber++;
        NSLog(@"block執(zhí)行了%d次",aNumber);
        [subscriber sendNext:@(aNumber)];
        [subscriber sendCompleted];
        return nil;
    }];
    
    [aSignal subscribeNext:^(id x) {
        NSLog(@"subscriber one: %@", x);
    }];
    
    [aSignal subscribeNext:^(id x) {
        NSLog(@"subscriber two: %@", x);
    }];

輸出:

2018-11-08 16:51:02.311068+0800 RACDemo[5714:1320947] block執(zhí)行了1次
2018-11-08 16:51:02.311278+0800 RACDemo[5714:1320947] subscriber one: 1
2018-11-08 16:51:02.311440+0800 RACDemo[5714:1320947] block執(zhí)行了2次
2018-11-08 16:51:02.311538+0800 RACDemo[5714:1320947] subscriber two: 2

rac_sequence.signal

這種情況并不常用,不過我們還是要分析一下。

RACSignal *letters = [@"A B C" componentsSeparatedByString:@" "].rac_sequence.signal;
NSLog(@"xxx");
[letters subscribeNext:^(NSString *x) {
    NSLog(@"1%@", x);
}];
NSLog(@"yyy");
[letters subscribeNext:^(NSString *x) {
    NSLog(@"2%@", x);
}];
NSLog(@"zzz");

輸出:

2018-11-08 17:55:46.474730+0800 RACDemo[6136:1391178] xxx
2018-11-08 17:55:46.475301+0800 RACDemo[6136:1391178] yyy
2018-11-08 17:55:46.475459+0800 RACDemo[6136:1391178] zzz
2018-11-08 17:55:46.475561+0800 RACDemo[6136:1391225] 1A
2018-11-08 17:55:46.475887+0800 RACDemo[6136:1391225] 2A
2018-11-08 17:55:46.476103+0800 RACDemo[6136:1391225] 1B
2018-11-08 17:55:46.476308+0800 RACDemo[6136:1391225] 2B
2018-11-08 17:55:46.476468+0800 RACDemo[6136:1391225] 1C
2018-11-08 17:55:46.476763+0800 RACDemo[6136:1391225] 2C

當輸出到zzz的時候,letters已經(jīng)添加了兩個subscriber。為什么第一次添加subscribeNext的時候不立即輸出,而是等到所有subscribeNext添加結(jié)束后才輸出呢?后面再開個源碼分析的文章來單獨說明。


RACMulticastConnection

  • 它可以避免像上面那樣每添加一個subscribeNext就執(zhí)行一次block。
  • 調(diào)用connect后會立即將f作用到value上(block會立即執(zhí)行),后面再添加任何數(shù)量的subscribeNext則不會執(zhí)行block了,副作用只執(zhí)行1次。
__block NSInteger index = 0;
    RACSignal *networkRequest = [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        NSLog(@"inner signal %ld", ++index);
        [subscriber sendNext:@(index)];
        [subscriber sendCompleted];
        return [RACDisposable disposableWithBlock:^{
            
        }];
    }];
    
    RACMulticastConnection *connection = [networkRequest multicast:[RACReplaySubject subject]];
    /*!調(diào)用connect后signal中的block會立即執(zhí)行,后面再調(diào)用subscribeNext則不會執(zhí)行block了,副作用h只執(zhí)行1次*/
    [connection connect];
    
    [connection.signal subscribeNext:^(NSNumber *index) {
        NSLog(@"subscriber one: %ld", index.integerValue);
    }];

    [connection.signal subscribeNext:^(NSNumber *index) {
        NSLog(@"subscriber two: %ld", index.integerValue);
    }];

輸出:

2018-11-08 17:05:54.997850+0800 RACDemo[5852:1349307] inner signal 1
2018-11-08 17:05:54.998385+0800 RACDemo[5852:1349307] subscriber one: 1
2018-11-08 17:05:54.998526+0800 RACDemo[5852:1349307] subscriber two: 1

doNext & doCompleted

  • 設(shè)置doNextdoCompleted回調(diào)函數(shù)后會得到一個新的signal后續(xù)需要使用新signal
  • doNext只是設(shè)置回調(diào)函數(shù),subscribeNext才會觸發(fā)signal執(zhí)行block。
  • signal中的[subscriber sendNext:@(subscriptions)];會先觸發(fā)doNext回調(diào),然后再執(zhí)行subscribeNext。
__block unsigned subscriptions = 0;
    NSLog(@"1");
    RACSignal *loggingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
        NSLog(@"enter signal block");
        subscriptions++;
        //會觸發(fā)doNext
        [subscriber sendNext:@(subscriptions)];
        //會觸發(fā)doCompleted
        [subscriber sendCompleted];
        return nil;
    }];
    
    /*!會返回一個新信號
     */
    NSLog(@"2");
    loggingSignal = [loggingSignal doCompleted:^{
        NSLog(@"doCompleted %u", subscriptions);
    }];
    NSLog(@"3");
    loggingSignal = [loggingSignal doNext:^(NSNumber *x) {
        NSLog(@"doNext %ld",x.integerValue);
    }];
    NSLog(@"4");
    //會立即執(zhí)行
    [loggingSignal subscribeNext:^(NSNumber *x) {
        NSLog(@"subscribeNext %ld",x.integerValue);
    }];
    NSLog(@"5");
    //會立即執(zhí)行
    [loggingSignal subscribeCompleted:^{
        NSLog(@"subscribeCompleted %u", subscriptions);
    }];
    NSLog(@"6");

輸出:

2018-11-10 12:03:08.946714+0800 RACDemo[1403:66623] 1
2018-11-10 12:03:08.949269+0800 RACDemo[1403:66623] 2
2018-11-10 12:03:08.949631+0800 RACDemo[1403:66623] 3
2018-11-10 12:03:08.949755+0800 RACDemo[1403:66623] 4
2018-11-10 12:03:08.950598+0800 RACDemo[1403:66623] enter signal block
2018-11-10 12:03:08.950693+0800 RACDemo[1403:66623] doNext 1
2018-11-10 12:03:08.950799+0800 RACDemo[1403:66623] subscribeNext 1
2018-11-10 12:03:08.950916+0800 RACDemo[1403:66623] doCompleted 1
2018-11-10 12:03:08.951015+0800 RACDemo[1403:66623] 5
2018-11-10 12:03:08.951110+0800 RACDemo[1403:66623] enter signal block
2018-11-10 12:03:08.951177+0800 RACDemo[1403:66623] doNext 2
2018-11-10 12:03:08.951253+0800 RACDemo[1403:66623] doCompleted 2
2018-11-10 12:03:08.951330+0800 RACDemo[1403:66623] subscribeCompleted 2
2018-11-10 12:03:08.951418+0800 RACDemo[1403:66623] 6

Transforming streams

map

  • map的用法是轉(zhuǎn)換stream中的值,然后返回一個新的stream
RACSequence *letters = [@"A B C" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *mapped = [letters map:^(NSString *value) {
    return [value stringByAppendingString:value];
}];
[mapped.signal subscribeNext:^(id x) {
    NSLog(x);
}];

輸出:AA、BB、CC


filter

  • filter方法會遍歷stream把返回YES的元素組成一個新的stream
RACSequence *numbers = [@"1 2 3 4 5 6" componentsSeparatedByString:@" "].rac_sequence;
//只要偶數(shù)
RACSequence *filtered = [numbers filter:^ BOOL (NSString *value) {
    return (value.intValue % 2) == 0;
}];
[filtered.signal subscribeNext:^(id x) {
    NSLog(x);
}];

輸出:2、46


concat

  • contact方法會拼接兩個stream。
  • 對兩個RACSequence調(diào)用concat效果如同對NSMutableArray使用addObjectsFromArray:
RACSequence *letters = [@"A B C" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *numbers = [@"1 2 3" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *concatenated = [letters concat:numbers];
[concatenated.signal subscribeNext:^(id x) {
    NSLog(x);
}];

輸出:A、BC、12、3


flatten

flatten for sequences

  • 適用于signal of signals類型。
  • 可以將二維的stream拍扁變成一維的strean。
  • 但是如果用在多維的signal of signals of signals則會出錯誤。
RACSequence *letters = [@"A B C" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *numbers = [@"1 2 3" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *sequenceOfSequences = @[ letters,numbers ].rac_sequence;
RACSequence *flattened = [sequenceOfSequences flatten];
[flattened.signal subscribeNext:^(id x) {
     NSLog(x);
}];

輸出:A、BC、12、3

flatten for Signal

  • 實際作用是merge將多個signal合并成一個。
  • 合并后任何一個signal發(fā)送數(shù)據(jù),都會觸發(fā)signalblock執(zhí)行。
RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *signalOfSignals = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
    [subscriber sendNext:letters];
    [subscriber sendNext:numbers];
    [subscriber sendCompleted];
    return nil;
}];
RACSignal *flattened = [signalOfSignals flatten];
flattened = [flattened doNext:^(NSString *x) {
    NSLog(@"doNext %@", x);
}];
[flattened subscribeNext:^(NSString *x) {
    NSLog(@"subscribeNext %@", x);
}];
[letters sendNext:@"A"];
[numbers sendNext:@"1"];
[letters sendNext:@"B"];
[letters sendNext:@"C"];
[numbers sendNext:@"2"];

輸出:

2018-11-10 14:42:04.989738+0800 RACDemo[2038:140686] doNext A
2018-11-10 14:42:04.989942+0800 RACDemo[2038:140686] subscribeNext A
2018-11-10 14:42:04.990053+0800 RACDemo[2038:140686] doNext 1
2018-11-10 14:42:04.990127+0800 RACDemo[2038:140686] subscribeNext 1
2018-11-10 14:42:04.990221+0800 RACDemo[2038:140686] doNext B
2018-11-10 14:42:04.990315+0800 RACDemo[2038:140686] subscribeNext B
2018-11-10 14:42:04.990440+0800 RACDemo[2038:140686] doNext C
2018-11-10 14:42:04.990535+0800 RACDemo[2038:140686] subscribeNext C
2018-11-10 14:42:04.990634+0800 RACDemo[2038:140686] doNext 2
2018-11-10 14:42:04.990715+0800 RACDemo[2038:140686] subscribeNext 2

flattenMap

flattenMap for sequence

  • 相當于先mapflatten。

ps:先后順序,先map可以修改stream流中的值,然后flatten可以保證生成一個一維的新stream。

RACSequence *numbers = [@"1 2 3" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *extended = [[numbers
                              map:^id(id value) {
                                  return @[value,value].rac_sequence;
                              }] flatten];
[extended.signal subscribeNext:^(id x) {
    NSLog(x);
}];

上下兩份代碼等價。

RACSequence *numbers = [@"1 2 3" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *extended = [numbers flattenMap:^(NSString *num) {//這段代碼等同于上面先map再flatten
        return @[ num, num ].rac_sequence;
}];
[extended.signal subscribeNext:^(id x) {
    NSLog(x);
}];

輸出:1、1、2、2、3、3
ps:這里不是輸出1122、33這里沒有字符串拼接,而是被拍扁了。

  • 在對RACSequence使用flattenMap返回empty并不會存入sequence中,而是會被忽略掉。
RACSequence *numbers = [@"1 2 3" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *edited = [numbers flattenMap:^(NSString *num) {
    if (num.intValue % 2 == 0) {
        //這里必須有返回值但是又不能返回nil,empty不會成為新RACSequence中的項。
        return [RACSequence empty];
    } else {
        NSString *newNum = [num stringByAppendingString:@"_"];
        return [RACSequence return:newNum];
    }
}];
[edited.signal subscribeNext:^(id x) {
    NSLog(x);
}];

輸出:1_3_

flattenMap for signal

  • flattenMap是根據(jù)前一個信號的參數(shù)創(chuàng)建一個新的信號,然后進行一次flatten進行拍扁。
/*!調(diào)用登錄函數(shù),成功后發(fā)送sendNext
 */
- (RACSignal *)signInSignal {
    return [RACSignal createSignal:^RACDisposable *(id subscriber){
        [self.signInService
            signInWithUsername:self.usernameTextField.text
            password:self.passwordTextField.text
            complete:^(BOOL success){
                [subscriber sendNext:@(success)];
                [subscriber sendCompleted];
            }];
            return nil;
    }];
}

/*!點擊按鈕后調(diào)用登錄
 */
- (void)flattenMapForSignal{
    [[[self.signInButton
        rac_signalForControlEvents:UIControlEventTouchUpInside]
        flattenMap:^id(id x){
            return [self signInSignal];
        }]
        subscribeNext:^(id x){
            NSLog(@"登錄結(jié)果: %@", x);
        }];
}

分析:
上例中當按鈕點擊后流中傳遞的是按鈕點擊的信號,通過flattenMap進行信號轉(zhuǎn)換。

  • map按鈕點擊的信號轉(zhuǎn)換成signInSignal的登錄信號。
  • 此后流中傳遞的就是登錄的信號了。
  • 所以我們后來subscribeNext接收到的將會是登錄的結(jié)果。

Combining signals

then

  • 開始原始信號等到它執(zhí)行結(jié)束之后使用新的值生成一個新的信號。
  • 常用的方法是在一個信號里執(zhí)行所有的副作用,然后返回一個新的信號。
RACSignal *letters = [@"A B C" componentsSeparatedByString:@" "].rac_sequence.signal;
RACSignal *sequenced = [[letters
                             doNext:^(NSString *letter) {
                                 NSLog(@"doNext %@", letter);
                             }]
                            then:^{
                                NSLog(@"then");
                                return [@"1 2 3" componentsSeparatedByString:@" "].rac_sequence.signal;
                            }];
[sequenced subscribeNext:^(id x) {
    NSLog(@"subscribeNext %@",x);
}];

輸出:

2018-11-10 15:24:03.646833+0800 RACDemo[2526:204404] doNext A
2018-11-10 15:24:03.647147+0800 RACDemo[2526:204404] doNext B
2018-11-10 15:24:03.647345+0800 RACDemo[2526:204404] doNext C
2018-11-10 15:24:03.647595+0800 RACDemo[2526:204404] then
2018-11-10 15:24:03.647890+0800 RACDemo[2526:204406] subscribeNext 1
2018-11-10 15:24:03.648029+0800 RACDemo[2526:204406] subscribeNext 2
2018-11-10 15:24:03.648158+0800 RACDemo[2526:204406] subscribeNext 3

merge

  • 將多個信號合并成一個信號,這些信號中任何一個收到值都會觸發(fā)新信號。
RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *merged = [RACSignal merge:@[ letters, numbers ]];
[merged subscribeNext:^(NSString *x) {
    NSLog(@"%@", x);
}];
[letters sendNext:@"A"];
[numbers sendNext:@"1"];
[letters sendNext:@"B"];
[letters sendNext:@"C"];
[numbers sendNext:@"2"];

輸出:A、1B、C、2


combineLatest: reduce:

  • 合并多個信號。
  • 觸發(fā)條件:集齊七顆龍珠才可以召喚一次神龍。
  • 每一個信號只保存最新的值,不是盞結(jié)構(gòu)。
RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *combined = [RACSignal
                           combineLatest:@[ letters, numbers ]
                           reduce:^(NSString *letter, NSString *number) {
                               return [letter stringByAppendingString:number];
                           }];
[combined subscribeNext:^(id x) {
    NSLog(@"%@", x);
}];
[letters sendNext:@"A"];
[letters sendNext:@"B"];
[numbers sendNext:@"1"];
//不是盞結(jié)構(gòu) 所以輸出B2 而不是A2
[numbers sendNext:@"2"];
[letters sendNext:@"C"];
[numbers sendNext:@"3"];

輸出:B1B2、C2C3


switchToLatest

  • 使用的場景是signal-of-signals,subscribeNext始終關(guān)注并返回最后一個signal的值。之前的signal再有新值則不會觸發(fā)subscribeNext
RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSubject *signalOfSignals = [RACSubject subject];
RACSignal *switched = [signalOfSignals switchToLatest];
[switched subscribeNext:^(NSString *x) {
    NSLog(@"%@", x);
}];

[signalOfSignals sendNext:letters];
[letters sendNext:@"A"];
[letters sendNext:@"B"];
    
[signalOfSignals sendNext:numbers];
/*!因為先前往signalOfSignals發(fā)送了numbers所以最新的信號變成了numbers,這里再往letters發(fā)送值則subscribeNext不會觸發(fā)*/
[letters sendNext:@"C"];
[numbers sendNext:@"1"];
    
[signalOfSignals sendNext:letters];
//這里又切換了最新的signal,所以再往numbers發(fā)送值則不會觸發(fā)subscribeNext。
[numbers sendNext:@"2"];
[letters sendNext:@"D"];

輸出:AB、1、D

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容