RACSignal分析

2,780 阅读7分钟

信号的使用,包括:创建信号、发送信号、订阅信号

创建信号

获取信号的方式有很多种:

  • 创建单元信号

  • 创建动态信号

  • 通过Cocoa桥接

  • 从别的信号变换而来

  • 由序列变换而来

单元信号

最简单的信号是单元信号,有4种:

// return信号:被订阅后,马上产生一个值事件,同时产生一个完成事件
RACSignal *signal1 = [RACSignal return:someObject];

// error信号:被订阅后,马上产生一个错误事件,同时产生一个完成事件
RACSignal *signal2 = [RACSignal error:someError];

// empty信号:被订阅后,马上产生一个空事件,同时产生一个完成事件
RACSignal *signal3 = [RACSignal empty];

// never信号:永远不产生事件,同时产生一个完成事件
RACSignal *signal4 = [RACSignal never]; 

动态信号

RACSignal *signal5 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
  [subscriber sendNext:@"1"];
  [subscriber sendNext:@"2"];
  [subscriber sendCompleted]; 
  return [RACDisposable disposableWithBlock:^{
    
  }];
}];

Cocoa桥接

RAC为大量的Cocoa类型提供便捷的信号桥接工具,如下是一些常见的桥接方式:

RACSignal *signal6 = [object rac_signalForSelector:@selector(setFrame:)];
RACSignal *signal7 = [control rac_signalForControlEvents:UIControlEventTouchUpInside];
RACSignal *signal8 = [object rac_willDeallocSignal];
RACSignal *signal9 = RACObserve(object, keyPath);

信号变换

RACSignal *signal10 = [signal1 map:^id(id value) {
    return someObject;
}];

序列变换

RACSignal *signal11 = sequence.signal;

订阅信号

订阅信号的方式有3种:

  • 通过subscribeNext:error:completed:方法订阅

  • RAC宏绑定

  • Cocoa桥接

subscribeNext:error:completed:是最基础的信号订阅方法;

RAC宏绑定 可以使用RAC()宏 如:RAC(view, backgroundColor) = signal;

Cocoa桥接 [object rac_liftSelector:@selector(someSelector:) withSignals:signal1, signal2, nil];

订阅过程

所谓订阅过程指的是信号被订阅的处理逻辑,如下是简单的例子:

RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"1"];    
    [subscriber sendNext:@"2"];
    [subscriber sendCompleted];     
    return [RACDisposable disposableWithBlock:^{
        NSLog(@"dispose");       // 当错误事件或者完成事件产生时,该block被调用
    }];
}];
[signal subscribeNext:^(id x) {
    NSLog(@"next value is :  %@", x);
} error:^(NSError *error) {
    NSLog(@"error : %@", error);
} completed:^{
    NSLog(@"completed");
}];

信号的各类操作

RACTuple , 就像NSArray数组:

RACTuple *tuple = RACTuplePack(@1, @"haha");
id first = tuple.first;
id second = tuple.second;
id last = tuple.last;
id index1 = tuple[1];

RACTupleUnpack(NSNumber *num, NSString *str) = tuple;

信号的操作一般分为单个信号的变换、多个信号的组合及高阶操作

单个信号的变换

分为值操作、数量操作、时间操作、副作用操作

值操作

map 做一个映射,返回新的信号

mapReplace 等价于返回同一个值得map

reduceEach,是map:的变体。当signalA的值事件包裹的数据是RACTuple类型时,才可以使用该操作,将RACTuple组合成一个

reduceApply,要求值事件包裹的数据类型是RACTuple,并且该RACTuple的第一个元素是一个block,后面的元素作为该block的参数传入,返回该block的执行结果。

逻辑操作:not, and, or

数量操作

filter 过滤

ignore 忽略

take 只取前n个

skip 过滤掉前n个

startWith 从某个元素开始

distinctUntilChanged 去重复

repeat 重复

retry [signalA retry:2]; 重试几次

collect 把所以集合到一个

aggregate 和sacn 类似但有区别 scan 实时输出,aggregate 最终结束才输出,出错则不输出

副作用操作

doNext doError doCompleted

initially finally

时间操作

delay 延迟

throttle:1 当signalA事件流产生一个值事件时,若1 s内没有其他的值事件产生,则signalB事件流中也会产生该值事件;当signalA事件流产生一个值事件时,若1s内有其他的值事件产生,则signalB会过滤掉该值事件,对于signalA中的最后一个值事件,signalB事件流中总会也包含它

多个信号的组合

concat 加在后面

merge 插入进来

zip 两个信号合成一个RACTuple

combineLatest a信号来一个值事件就和b信号最近的值事件组合

sample 类似取景和快门

takeUntil signalC事件流中的事件和signalA一一对应,直到signalB事件流中出现了信号,事件流就终结。

信号的高阶操作

switchToLatest 读到新的信号就把之前的信号关闭
if then else 其实就是switchToLatest的实现

+ (RACSignal *)if:(RACSignal *)boolSignal 
        then:(RACSignal *)trueSignal 
    else:(RACSignal *)falseSignal {
    .....  
    return [[[boolSignal    map:^(NSNumber *value) {      
        NSCAssert([value isKindOfClass:NSNumber.class], @"Expected %@ to send BOOLs, not %@", boolSignal, value);​      
        return (value.boolValue ? trueSignal : falseSignal);    
    }] switchToLatest]    
    setNameWithFormat:@"+if: %@ then: %@ else: %@", boolSignal, trueSignal, falseSignal];
}

代替代理

  • rac_signalForSelector 用于代替代理

代替 KVO

  • rac_valuesAndChangesForKeyPath 用于监听某个对象的某个属性的改变

代替事件监听

  • rac_signalForControlEvents 用于监听某个事件

代替通知

  • rac_addObserverForName 用于监听某个通知,且不需要在 - (void)dealloc 中移除监听

监听文本框文字改变

  • rac_textSignal 用于监听文本框文字变化

监听手势

  • rac_gestureSignal用于监听手势操作

多个请求完成时,再执行后继操作

  • rac_liftSelector:withSignalsFromArray:Signals 当传入的 Signals,每一个 Signal 都至少 sendNext 过一次,就会去触发第一个 selector 参数的方法。

信号的相关操作

  • bind :函数会返回一个新的信号 N。整体思路是对原信号 O 进行订阅,每当信号 O 产生一个值就将其转变成一个中间信号 M ,并马上订阅 M ,之后将信号M的输出作为新信号 N 的输出。
  • map \ flattenMap :用于把源信号内容映射成新的内容(信号)。
  • concat :组合,按一定顺序拼接信号,当多个信号发出的时候,有顺序的接收信号。
  • then :用于连接两个信号,当第一个信号完成,才会连接 then 返回的信号。
  • merge :把多个信号合并为一个信号,任何一个信号有新值的时候就会调用。
  • zipWith :把两个信号压缩成一个信号,只有当两个信号同时发出信号内容时,并且把两个信号的内容合并成一个元组,才会触发压缩流的 next 事件。
  • combineLatest :将多个信号合并起来,并且拿到各个信号的最新的值,必须每个合并的 signal至少都有过一次 sendNext ,才会触发合并的信号。
  • reduce :聚合,用于信号发出的内容是元组,把信号发出元组的值聚合成一个值。
  • filter :过滤信号,使用它可以获取满足条件的信号。
  • ignore :忽略某些值的信号,使用 RACObserve 时可配合使用,其实现由 filter 完成。
  • distinctUntilChanged :实现是用 bind 来完成的,每次变换中都记录一下原信号上一次发送过来的值,并与这一次进行比较,如果是相同的值,就「吞」掉,返回 empty 信号。只有和原信号上一次发送的值不同,变换后的新信号才把这个值发送出来。
  • take :从开始一共取 N 次的信号。
  • takeLast :取最后 N 次的信号,前提条件:订阅者必须调用完成,因为只有完成,才知道总共有多少信号。
  • takeUntil :获取信号直到某个信号执行完成。
  • skip :跳过几个信号,不接受。
  • switchToLatest :用于 signalOfSignals(信号的信号),有时候信号也会发出信号,会在 signalOfSignals 中,获取 signalOfSignals 发送的最新信号。
  • doNext :执行 next 之前,会先执行这个 Block 。
  • doCompleted :执行 sendCompleted 之前,会先执行这个Block 。
  • timeout :超时,可以让一个信号在一定的时间后,自动报错。
  • interval :定时:每隔一段时间发出信号。
  • delay :延迟发送 next
  • retry :重试,只要失败,就会重新执行创建信号中的 block ,直到成功。
  • replay :重放,当一个信号被多次订阅,反复播放内容。
  • throttle :节流,当某个信号发送比较频繁时,可以使用节流,在某一段时间不发送信号内容,过了一段时间获取信号的最新内容发出。

ReactiveCocoa 常见宏

  • RAC(TARGET, ...) 用于绑定某个对象的某个属性
  • RACObserve(TARGET, KEYPATH) 用于监听某个对象的某个属性,返回的是信号
  • @weakify(Obj) & @strongify(Obj) 配套使用