深入理解代替单纯记忆
本文记录一下ReactiveCocoa(Objective C版本)中常用的操作符,算是备忘吧
本文基于
ReactiveObjc 2.5.5版本
前提
为了后面讲解方便,统一一下一些名称的概念
- 我们一般都将RACSignal称为
信号,有人也把它叫做信号流,其实不论怎么称呼,只要了解到**RACSignal用来表示数据流**即可 - 本文中,我们也将
RACSignal称为信号,信号可以发送数据,发送数据的信号即为数据流 - 当然,信号发送的数据也可以是信号
bind、flatten、flattenMap、map
bind
- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block
typedef RACStream * (^RACStreamBindBlock)(id value, BOOL *stop);
分析bind的源码,可知道具体操作是:
- 该方法会创建一个新信号N并返回
- 当N被订阅时,会立即订阅原信号O
- 当O发送数据时,通过bind的参数block,产生中间信号M,然后订阅信号M
- 中间信号发送数据时,会通过N将数据发送出去
结合上图,换成更容易理解的话就是
- 原信号(original signal),每发送一个数据(右侧红球)
- block会收到原信号发送的数据(value),构建新信号M,比如图中间顶部的绿色球所在的箭头即为新信号M
- 当M信号发送数据时(绿色球即代表M发送的数据),最终订阅者就会收到该数据(绿色球)。紫色、蓝色等球也是类似的
- 总结为一句话,原信号被转化为了block中的信号,block中的信号发送什么数据,最终订阅者就能收到什么数据
bind是其他几个操作(flattenMap、map、flatten)的基础
flattenMap
- (instancetype)flattenMap:(RACStream * (^)(id value))block
其内部也是调用的bind,做的事情是一样的,但功能没有bind强大
flatten
- (instancetype)flatten
flatten为关键字的方法在其他语言中也很常见,有人将它翻译为拍平,比如将一个数组的数组--[[1, 2], [3, 4]]拍平后就是[1, 2, 3, 4]
在RAC中也可以这么理解,需要注意的是,只有信号的信号才能执行flatten进行拍平
- flatten返回一个新的信号,该信号将原信号(原信号发送的数据也是信号)拍平
- 订阅flatten返回的信号,收到的数据将是原信号中的每个信号发送的数据
map
RACStream方法
- (instancetype)map:(id (^)(id value))block
内部使用flattenMap进行封装,block中只需要根据接收到的原信号发过来的数据,转换为返回新信号中新数据
filter
- (instancetype)filter:(BOOL (^)(id value))block
返回一个新的信号,新的信号数据由,满足block中条件的原信号数据组成
RACSignal *signal = [signal1 filter:^BOOL(NSString *value) {
// 满足该条件的数据会出现在signal中
return value.length > 20;
}];
ignore
- (instancetype)ignore:(id)value
返回一个RACStream,新的数据流中,忽略参数指定的值
内部会使用
isEqual判断是否满足忽略条件
[[self.inputTextField.rac_textSignal ignore:@"sunny"] subscribeNext:^(NSString *value) {
NSLog(@"`sunny` could never appear : %@", value);
}];
ignoreValues
- (RACSignal *)ignoreValues
忽略所有的next值,只接受error和complete事件
distinctUntilChanged
RACStream的方法
- (instancetype)distinctUntilChanged
对原信号的数据做如下处理,每收到一个数据,与前一个数据比较,如果相等(通过isEqual比较),则忽略,直到结束,或者遇到不同的数据
如下的例子中,使用distinctUntilChanged可以避免label多次更新
RAC(self.label, text) = [RACObserve(self.user, username) distinctUntilChanged];
self.user.username = @"sunnyxx"; // 1st
self.user.username = @"sunnyxx"; // 2nd
self.user.username = @"sunnyxx"; // 3rd
take
take:
- (instancetype)take:(NSUInteger)count
RACStream的方法
只取前count个next值
takeLast:
- (RACSignal *)takeLast:(NSUInteger)count
取后count个数据
takeUntilBlock:
RACStream方法
- (instancetype)takeUntilBlock:(BOOL (^)(id x))predicate
直到block中判断返回为YES时,停止取值
takeWhileBlock:
RACStream方法
- (instancetype)takeWhileBlock:(BOOL (^)(id x))predicate
直到block中为NO时,停止取值
skip
skip系列和take系列方法很像,可以类比学习,此处不赘述
- (instancetype)skip:(NSUInteger)skipCount- (instancetype)skipUntilBlock:(BOOL (^)(id x))predicate- (instancetype)skipWhileBlock:(BOOL (^)(id x))predicate
concat
RACStream方法
- (instancetype)concat:(RACStream *)stream
将两个数据流的数据,按照先receiver后stream的顺序串联起来
concat
- (RACSignal *)concat
该方法只能用于信号的信号,将信号的信号的数据串联起来
then
- (RACSignal *)then:(RACSignal * (^)(void))block
- block中需要返回一个signal
- then方法会等待receiver发送数据结束,即收到complete数据,然后再订阅block中的信号
- then方法返回的信号中,只有block中信号的数据
merge
- (RACSignal *)merge:(RACSignal *)signal
返回的新信号的数据,是两个信号的数据合到一起的数据,收到数据的先后顺序按照两个信号实际发出信号的时间为准
RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *merged = [RACSignal merge:@[ letters, numbers ]];
// Outputs: A 1 B C 2
[merged subscribeNext:^(NSString *x) {
NSLog(@"%@", x);
}];
[letters sendNext:@"A"];
[numbers sendNext:@"1"];
[letters sendNext:@"B"];
[letters sendNext:@"C"];
[numbers sendNext:@"2"];
reduce
reduceEach
reduceEach是RACStream的方法
- (instancetype)reduceEach:(id (^)())reduceBlock
reduce这个词此处可以翻译为归纳
- reduce的作用是化零为整,即收到的是零散的数据,输出的是一个数据
- 那么该方法要求,receiver发送的数据必须是零散数据,即
RACTuple类型 - reduceBlock的参数就是
RACTuple的每一项 - 通过reduceBlock中加工后,返回的数据,则是reduce方法产生的新信号所发送的数据
RACSignal *signal1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:RACTuplePack(@(1), @"string")];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signal = [signal1 reduceEach:^id(NSNumber *first, NSString *second){
return first;
}];
[signal subscribeNext:^(id x) {
NSLog(@"next value %@", x);
} completed:^{
NSLog(@"complete");
}];
// output
// next value 1
// complete
参考
scanWithStart
scanWithStart是RACStream的方法
- (instancetype)scanWithStart:(id)startingValue reduce:(id (^)(id running, id next))reduceBlock
- startingValue表示一个初始值,内部会遍历信号中的每一个值,执行reduceBLock,初次的running值就是startingValue,之后每一次得到的值时reduceBlock返回的内容
- 其实reduceBlock就像一个累加器一样
switchToLatest
- (RACSignal *)switchToLatest
该方法与flatten类似,也是只能信号的信号才能执行
- 图中右上角的红球,表示的是原信号O发送的数据,也是信号
- 原信号O发送了很多个信号,但阅了
switchToLatest返回的信号后,最终收到的数据却只有原信号发送的最新的信号L发送的数据 - 从图中来看,原信号O开始发送的信号中,有发送绿色球数据,有的紫色紫色球,这些数据最终都没有收到,最终真正收到的数据是,O发送的最后一个信号发送的三个咖啡色的球
参考部分是一个switchToLatest的经典应用场景
参考
combineLatestWith:
- (RACSignal *)combineLatestWith:(RACSignal *)signal
将receiver最新的值与参数signal的每个值合并到一起
- 两个信号都发送complete时,合并后的信号也会发送complete
zip:
zip是RACStream中定义的方法,其子类RACSignal和RACSequence都进行了实现
这里我们仅对RACSignal的实现进行说明
- (RACSignal *)zipWith:(RACSignal *)signal
- 从当前signal和待zip的signal中,从前往后,各取一个next数据,合并为一个
RACTuple的值,通过新信号发送出去 - 如果两个signal中的next值数量不匹配,则舍弃后面不匹配的next值
- 两个signal都complete时,新信号发送complete
scanWithStart
combinePreviousWithStart:reduce
- (instancetype)combinePreviousWithStart:(id)start reduce:(id (^)(id previous, id next))reduceBlock
return
RACSignal *signal = [RACSignal return:@1];
[signal subscribeNext:^(id x) {
NSLog(@"%@", x);
}
error:^(NSError *error) {
NSLog(@"error!");
}
completed:^{
NSLog(@"complete!");
}];
// 打印结果
// 1
// complete
empty
RACSignal *signal = [RACSignal empty];
[signal subscribeNext:^(id x) {
NSLog(@"%@", x);
}
error:^(NSError *error) {
NSLog(@"error!");
}
completed:^{
NSLog(@"complete!");
}];
// 打印结果
// complete
catch
- (RACSignal *)catch:(RACSignal * (^)(NSError *error))catchBlock
- 返回一个新信号N
- 当receiver发生error时,catchBlock中收到相应错误信息,并返回一个新的信号M,同时订阅M,自此以后新信号N中的数据便是M中的数据了
- 当receiver不发生error时,数据仍会通过N进行发送
- 还有一个精简的方法是
- (RACSignal *)catchTo:(RACSignal *)signal,意思是相同的- 比如
[A catchTo: B]表示,返回一个新信号,其中的数据是,如果A不发生错误就是A的数据,如果A发生错误,则数据边来源于B
- 比如
throttle
- (RACSignal *)throttle:(NSTimeInterval)interval
throttle动词形式翻译为节流阀
该方法接收一个时间间隔i,当receiver连续发送next时,如果这几个next数据在i之内,则只保留最后一个