ReactiveCocoa框架理解二

信号类还提供了一列方法,可以对信号传递的数据进行操作。

1.map:方法

map:方法可以将信号传递的数据进行映射和转换,demo如下:

RACSignal *signal = [RACSignal create:^(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"test"]; //传递"test"
}];
RACSignal *mappedSignal = [signal map:^id(NSString *value) {
    return @(value.length); //映射成"test"字符串的长度
}];
[mappedSignal subscribeNext:^(id x) {
    NSInteger len = [(NSNumber *)x integerValue];
    NSLog(@"%@",@(len)); //输出结果,结果是4
} completed:^{
    NSLog(@"completed");
}];

上一篇已经分析了create:方法,创建了一个RACDynamicSignal类型的信号signal,当执行订阅subscriber时会触发didSubscribe回调,调用[subscriber sendNext:@"test"]。本例不是由signal直接订阅subscriber,而是map方法后新的mappedSignal对象订阅。map:方法如下:

- (RACSignal *)map:(id (^)(id value))block {
    return [super map:block]; //调用父类(RACStrem)的map:方法
}

- (instancetype)map:(id (^)(id value))block {
    NSCParameterAssert(block != nil);
    Class class = self.class;
    //调用flattenMap:方法
    return [[self flattenMap:^(id value) {
        return [class return:block(value)];
    }] setNameWithFormat:@"[%@] -map:", self.name];
}

首先调用父类RACStrem的map:方法,接着调用flattenMap:方法,该方法默认由RACStrem类实现,如下:

- (instancetype)flattenMap:(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];
}

实质是通过bind方法创建一个新的信号。但是由于子类RACSignal重现了flattenMap:方法,故不会调用bind:方法,而是调用如下方法:

- (RACSignal *)flattenMap:(RACSignal * (^)(id value))signalBlock {
    //创建一个RACDynamicSignal,订阅subscriber时会触发didSubscribe回调
    return [[RACSignal
        create:^(id<RACSubscriber> subscriber) {
            __block volatile int32_t subscriptions = 0;
            //1.定义一个回调subscribeSignal,传入signal和nextBlock作为参数
            void (^subscribeSignal)(RACSignal *, void (^)(id)) = ^(RACSignal *signal, void (^nextBlock)(id)) {
                __block RACDisposable *savedDisposable;
                //订阅信号
                [signal subscribeSavingDisposable:^(RACDisposable *disposable) {
                    savedDisposable = disposable;

                    OSAtomicIncrement32(&subscriptions);
                    [subscriber.disposable addDisposable:savedDisposable];
                } next:nextBlock error:^(NSError *error) {
                    [subscriber sendError:error];
                } completed:^{
                    [subscriber.disposable removeDisposable:savedDisposable];
                    if (OSAtomicDecrement32(&subscriptions) == 0) [subscriber sendCompleted];
                }];
            };
            //2.触发subscribeSignal回调,传入信号和nextBlock
            subscribeSignal(self, ^(id x) {
                //创建一个新的signal
                RACSignal *innerSignal = signalBlock(x);

                if (innerSignal == nil) return;
                NSCAssert([innerSignal isKindOfClass:RACSignal.class], @"Expected a RACSignal, got %@", innerSignal);

                //3.再次调用subscribeSignal回调,传入新的innerSignal和nextBlock
                subscribeSignal(innerSignal, ^(id x) {
                    [subscriber sendNext:x]; //4.初始订阅者的next事件
                });
            });
        }]
        setNameWithFormat:@"[%@] -flattenMap:", self.name];
}

这是一个比较长的方法,根据注释,我们将它分成4个部分分析。

1.首先我们将demo中调用flattenMap:方法的信号对象记为signal,调用create:方法返回一个新的RACDynamicSignal,记为mappedSignal。

2.mappedSignal在demo中调用subscribeNext:completed:方法时,创建一个subscriber,记为subscriber0,同时触发mappedSignal的didSubscribe回调,进入步骤1。

3.首先定义一个subscribeSignal方法,传入两个参数,分别是要订阅的信号和订阅时执行的nextBlock,然后进行signal的订阅。

4.调用subscribeSignal方法,传入signal,触发signal的didSubscribe逻辑,在demo中是传递触发next事件,传入"test",如下:

RACSignal *signal = [RACSignal create:^(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"test"]; //传递"test"
}];

5.传回的参数x为"test",然后执行signalBlock(x),signalBlock由上一级map:方法传入,返回一个RACReturnSignal对象,内部value值是外层转化后的值,即字符串"test"的长度4,如下:

- (instancetype)map:(id (^)(id value))block {
    NSCParameterAssert(block != nil);
    Class class = self.class;
    return [[self flattenMap:^(id value) {
        return [class return:block(value)]; //返回一个新的RACReturnSignal对象,value是block转化后的值,即为4
    }] setNameWithFormat:@"[%@] -map:", self.name];
}

6.RACReturnSignal对象就是innerSignal,然后再次调用subscribeSignal方法,传入innerSignal和相应的nextBlock。前一篇文章分析过,对RACReturnSignal类型的对象innerSignal订阅,attachSubscriber:方法会直接触发nextBlock,传递value值,即4。最后在nextBlock中触发初始订阅者subscriber0的next事件,传递4,并输出。

2.filter:方法

filter:方法可以过滤信号的值,来决定后续数据是否继续传递,demo如下:

RACSignal *signal = [RACSignal create:^(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"test"];
}];
RACSignal *filteredSignal = [signal filter:^BOOL(NSString *value) {
    if (value.length>=4) { //字符串字数大于等于4,信号继续传递
        return YES;
    } else { //字符串字数小于4,信号终止传递
        return NO;
    }
}];
[filteredSignal subscribeNext:^(id x) {
    NSString *str = (NSString *)x; 
    NSLog(str); //输出"test"
} completed:^{
    NSLog(@"completed");
}];

在filter:方法里面通过返回YES或者NO来决定信号数据是否继续传递,demo中根据字符串的个数来判断。如果返回YES,则输出字符串,否则不输出。filter:方法如下:

- (RACSignal *)filter:(BOOL (^)(id value))block {
    return [super filter:block]; //调用基类RACStream的filter:方法
}

- (instancetype)filter:(BOOL (^)(id value))block {
    NSCParameterAssert(block != nil);
    Class class = self.class;
    //RACSignal的flatternMap:方法
    return [[self flattenMap:^ id (id value) {
        if (block(value)) {
            return [class return:value];
        } else {
            return class.empty;
        }
    }] setNameWithFormat:@"[%@] -filter:", self.name];
}

调用基类RACStream的filter:方法,和map:方法类似,最终也会调用flattenMap:方法,不同的是,filter:方法传递的block逻辑不一样,如果外层block返回YES,则返回一个RACReturnSignal,内部value仍然是原值,如果外层返回NO,则返回一个RACEmptySignal。

回顾一下flattenMap:方法内部处理逻辑,如下:

- (RACSignal *)flattenMap:(RACSignal * (^)(id value))signalBlock {
    return [[RACSignal
        create:^(id<RACSubscriber> subscriber) {
            __block volatile int32_t subscriptions = 0;

            void (^subscribeSignal)(RACSignal *, void (^)(id)) = ^(RACSignal *signal, void (^nextBlock)(id)) {
                __block RACDisposable *savedDisposable;

                [signal subscribeSavingDisposable:^(RACDisposable *disposable) {
                    savedDisposable = disposable;

                    OSAtomicIncrement32(&subscriptions);
                    [subscriber.disposable addDisposable:savedDisposable];
                } next:nextBlock error:^(NSError *error) {
                    [subscriber sendError:error];
                } completed:^{
                    //RACReturnSignal和RACEmptySignal在attachSubscriber都会触发completed事件
                    [subscriber.disposable removeDisposable:savedDisposable];
                    if (OSAtomicDecrement32(&subscriptions) == 0) {
                        [subscriber sendCompleted];
                    }
                }];
            };

            subscribeSignal(self, ^(id x) {
                RACSignal *innerSignal = signalBlock(x);
                
                if (innerSignal == nil) return;
                NSCAssert([innerSignal isKindOfClass:RACSignal.class], @"Expected a RACSignal, got %@", innerSignal);

                subscribeSignal(innerSignal, ^(id x) {
                    [subscriber sendNext:x];
                });
            });
        }]
        setNameWithFormat:@"[%@] -flattenMap:", self.name];
}

如果innerSignal是RACReturnSignal对象,则再次调用subscribeSignal方法,逻辑和map:方法一样。如果innerSignal是RACEmptySignal对象,则再次调用subscribeSignal方法,信号订阅时,会触发RACEmptySignal的attachSubscriber:方法,如下:

- (void)attachSubscriber:(RACLiveSubscriber *)subscriber {
    NSCParameterAssert(subscriber != nil);
    [subscriber sendCompleted]; //completed事件
}

只会触发completed事件,不会触发subscriber的next事件,也就不会触发初始订阅者subscriber0的next事件,不会输出。从而实现filter过滤信号数据的作用。

ignore:方法是在filter:方法的基础上封装了一下,实现指定信号数据的忽略。

- (instancetype)ignore:(id)value {
    return [[self filter:^ BOOL (id innerValue) {
        return innerValue != value && ![innerValue isEqual:value];
    }] setNameWithFormat:@"[%@] -ignore: %@", self.name, [value rac_description]];
}
3.concat:方法

concat:方法用于将若干个signal按顺序连接,signal按照先后顺序依次订阅subscriber。demo如下:

RACSignal *signal1 = [RACSignal create:^(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"one"];//发送next事件
    [subscriber sendCompleted];//发送completed事件
}];
RACSignal *signal2 = [RACSignal create:^(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"two"];//发送next事件
    [subscriber sendCompleted];//发送completed事件
}];
[[signal1 concat:signal2] subscribeNext:^(id x) {
    NSString *str = (NSString *)x;
    NSLog(str); //输出字符串
} completed:^{
    NSLog(@"completed");//输出"completed"
}];

执行demo,输出的顺序是"one","two","completed"。分析concat:方法,如下:

- (RACSignal *)concat:(RACSignal *)signal {
    return [[RACSignal create:^(id<RACSubscriber> subscriber) {
        [self subscribeSavingDisposable:^(RACDisposable *disposable) {
            [subscriber.disposable addDisposable:disposable];
        } next:^(id x) {
            [subscriber sendNext:x]; //触发next事件
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            [signal subscribe:subscriber]; //在completed事件回调中触发下一个signal的订阅逻辑
        }];
    }] setNameWithFormat:@"[%@] -concat: %@", self.name, signal];
}

在demo中,首先signal1被订阅,sendNext:方法触发next事件,将数据传递给subscriber,输出"one"。然后sendCompleted:方法,触发completed事件,引起下一个信号对象signal2的订阅逻辑。如果signal1不调用sendCompleted方法,则结束流程。subscriber订阅signal2后,sendNext:方法触发next事件,输出字符串"two",sendCompleted方法触发completed事件,输出"completed"。

4.merge:方法

merge:方法将多个信号连接起来,实现多个信号依次被订阅。demo如下:

RACSignal *signal1 = [RACSignal create:^(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"test1"];
}];
RACSignal *signal2 = [RACSignal create:^(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"test2"];
}];
RACSignal *mergeSignal = [signal1 merge:signal2];
[mergeSignal subscribeNext:^(id x) {
    NSLog(@"%@",x); //依次输出"test1"、"test2"
}];

mergerSignal订阅subscriber时,依次执行signal1和signal2的didSubscribe回调,输出"test1","test2"。

merge:方法实现如下:

- (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 [[copiedSignals.rac_signal flatten] setNameWithFormat:@"+merge: %@", copiedSignals];
}

首先将signal1和signal2存入一个数组中,然后执行NSArray的rac_signal方法,创建一个RACDynamicSignal,订阅时,依次触发next事件,并且将signal1和signal2作为参数传递。

- (RACSignal *)rac_signal {
    NSArray *collection = [self copy];
    return [[RACSignal create:^(id<RACSubscriber> subscriber) {
        for (id obj in collection) {
            [subscriber sendNext:obj];
            if (subscriber.disposable.disposed) return;
        }
        [subscriber sendCompleted];
    }] setNameWithFormat:@"%@ -rac_signal", self.rac_description];
}

flatten:方法调用flattenMap:方法,根据上文对flattenMap:方法的分析,首先依次触发next事件,传递signal1和signal2,然后调用signalBlock,返回value。然后调用subscribeSignal()方法,触发next事件,依次输出test1和test2。

- (instancetype)flatten {
    __weak RACStream *stream __attribute__((unused)) = self;
    return [[self flattenMap:^(id value) {
        return value;
    }] setNameWithFormat:@"[%@] -flatten", self.name];
}
5.zipWith:方法

zipWith:方法的作用是将两个信号发出的数据压缩成一个传递,即subscriber调用一次sendNext,demo如下:

RACSignal *signal1 = [RACSignal create:^(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"test1"];
    [subscriber sendCompleted];
}];
RACSignal *signal2 = [RACSignal create:^(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"test2"];
    [subscriber sendCompleted];
}];
RACSignal *zipSignal = [signal1 zipWith:signal2];
[zipSignal subscribeNext:^(id x) {
    RACTuple *tuple = (RACTuple *)x;
    for (NSString *value in tuple.array) {
        NSLog(@"%@",value);
    }
} completed:^{
    NSLog(@"completed");
}];

signal1和signal2被zipWith:方法压缩成一个信号zipSignal,输出的内容是一个RACTuple对象,包含signal1和signal2发送的数据"test1"和"test2",依次输出。下面分析一下zipWith:方法:

- (RACSignal *)zipWith:(RACSignal *)signal {
    NSCParameterAssert(signal != nil);
    //创建一个RACDynamic信号
    return [[RACSignal create:^(id<RACSubscriber> subscriber) {
        //管理signal1的数据集合
        __block BOOL selfCompleted = NO;
        NSMutableArray *selfValues = [NSMutableArray array];
        //管理signal2的数据集合
        __block BOOL otherCompleted = NO;
        NSMutableArray *otherValues = [NSMutableArray array];
        //是否触发subscriber的completed事件
        void (^sendCompletedIfNecessary)(void) = ^{
            @synchronized (selfValues) {
                BOOL selfEmpty = (selfCompleted && selfValues.count == 0);
                BOOL otherEmpty = (otherCompleted && otherValues.count == 0);
                if (selfEmpty || otherEmpty) [subscriber sendCompleted];
            }
        };
        //是否触发subscriber的next事件
        void (^sendNext)(void) = ^{
            @synchronized (selfValues) {
                if (selfValues.count == 0) return;
                if (otherValues.count == 0) return;

                RACTuple *tuple = [RACTuple tupleWithObjects:selfValues[0], otherValues[0], nil];
                [selfValues removeObjectAtIndex:0];
                [otherValues removeObjectAtIndex:0];

                [subscriber sendNext:tuple];
                sendCompletedIfNecessary();
            }
        };
        //signal1被订阅
        [self subscribeSavingDisposable:^(RACDisposable *disposable) {
            [subscriber.disposable addDisposable:disposable];
        } next:^(id x) {
            @synchronized (selfValues) {
                [selfValues addObject:x ?: RACTupleNil.tupleNil];
                sendNext();
            }
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            @synchronized (selfValues) {
                selfCompleted = YES;
                sendCompletedIfNecessary();
            }
        }];
        //signal2被订阅
        [signal subscribeSavingDisposable:^(RACDisposable *disposable) {
            [subscriber.disposable addDisposable:disposable];
        } next:^(id x) {
            @synchronized (selfValues) {
                [otherValues addObject:x ?: RACTupleNil.tupleNil];
                sendNext();
            }
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            @synchronized (selfValues) {
                otherCompleted = YES;
                sendCompletedIfNecessary();
            }
        }];
    }] setNameWithFormat:@"[%@] -zipWith: %@", self.name, signal];
}

该方法包含如下步骤:

1、维护selfValues和otherValues两个数组,里面分别包含signal1和signal2发出的信号数据,以及selfCompleted和otherCompleted两个变量,表示信号响应是否完成。

2、signal1订阅,触发next事件,将"test1"加入selfValues,触发sendNext()方法,由于此时otherValues尚没有数据,直接return。signal1的触发subscriber的completed事件,selfCompleted变为YES。

3、signal2订阅,触发next事件,将"test2"加入otherValues,触发sendNext()方法,将selfValues和otherValues数组中的第一个元素去除,拼成一个RACTuple对象,作为信号数据传递给subscriber的next事件,输出tuple中的各个元素。

4、signal2触发completed事件,otherCompleted变为YES。调用sendCompletedIfNecessary(),由于selfCompleted或者otherCompleted变量置为Yes,且selfValues和otherValues数组中元素为空,触发subscriber的completed事件,输出"completed"。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容