本文基于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è)置
doNext或doCompleted回調(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、4、6
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、B、C、1、2、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、B、C、1、2、3
flatten for Signal
- 實際作用是
merge將多個signal合并成一個。 - 合并后任何一個
signal發(fā)送數(shù)據(jù),都會觸發(fā)signal的block執(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
- 相當于先
map再flatten。
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:這里不是輸出11、22、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、1、B、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"];
輸出:B1、B2、C2、C3
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"];
輸出:A、B、1、D