RAC 即 ReactiveCocoa,是 Github 开源的函数响应式编程框架。可以简化代码,使一个功能的代码集中到一起,提高了代码聚合性。

  1. 创建信号(冷信号)
  2. 订阅信号(热信号)
  3. 发送信号
// 1. 创建信号,点进去可以看到:
// 它其实创建了一个RACDynamicSignal信号,并保存了didSubscribe这个Block
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"创建了信号");

// 3. 发送信号,点进去可以看到:
// 执行之前所保存的nextBlock
[subscriber sendNext:@"ZM"];
NSLog(@"发送了信号");

return [RACDisposable disposableWithBlock:^{
// 取消订阅时的回调
}];
}];

// 2. 订阅信号,点进去可以看到:
// 创建了一个RACSubscriber订阅者,并保存了一个nextBlock,
// 同时执行之前所保存的didSubscribe这个Block
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"订阅了信号");
NSLog(@"%@", x);

}];

应用场景

  • 代替代理(更少代码)rac_signalForSelector:
  • 代替 KVO rac_valuesAndChangesForKeyPath:
  • 监听事件(用于 MVVM)rac_signalForControllerEvents:
  • 代替通知(自动 remove)rac_addObserverForName:
  • 监听文本框内容改变 rac_textSignal:
  • 处理当界面有多次请求时,需要都获取到数据时,才能展示界面 rac_liftSelector:WithSignalFromArray:Signals:

常用方法

map & flattenMap

这是两个映射方法,将源信号内容映射成新的内容,就像 signal 管道上的中间处理器,从这里走过的 signal 都会经过一段处理后,变成新的 signal 继续传输。

map
// map,将输出NSNumber的signal转换成输出NSString
RACSignal *mapSignal = [[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@(1)];
return nil;
}] map:^id _Nullable(id _Nullable value) {
return [NSString stringWithFormat:@"%@", value];
}];
[mapSignal subscribeNext:^(id _Nullable x) {
// NSString类型
NSLog(@"x");
}];
flattenMap
// flattenMap,将输出NSNumber的signal转换成输出NSString
RACSignal *mapSignal = [[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@(1)];
return nil;
}] flattenMap:^__kindof RACSignal * _Nullable(id _Nullable value) {
return [RACReturnSignal return:[NSString stringWithFormat:@"%@", value]];
}];
[mapSignal subscribeNext:^(id _Nullable x) {
// NSString类型
NSLog(@"x");
}];

两者之间的区别就在于,map 中 Block 返回转换对象,flattenMap 返回转换对象的信号。一般信号发出的值不是信号,使用 map;如果是信号则使用 flattenMap,它可以处理信号中的信号。

merge

把多个信号合并成一个信号,只需订阅这一个信号就相当于订阅了多个信号,任何一个信号有新值的时候都会触发调用。

merge
RACSignal *signal1 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
[subscriber sendNext:@(1)];
return nil;
}];
RACSignal *signal2 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@(2)];
return nil;
}];
RACSignal *signal3 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@(3)];
return nil;
}];
RACSignal *mergeSignal = [RACSignal merge:@[signal1, signal2, signal3]];
[mergeSignal subscribeNext:^(id _Nullable x) {
// 分别输出1,2,3
NSLog(@"%@", x);
}];

concat

将多个信号有顺序的连接起来,按照顺序接收信号,但是一定要之前的信号完成了才能发送下一个信号。

concat
RACSignal *signal1 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
[subscriber sendNext:@(1)];
// 发送信号完成,表示不再订阅了,内部会自动调用[RACDisposable disposable]取消订阅信号。
[subscriber sendCompleted];
return nil;
}];
RACSignal *signal2 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@(2)];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signal3 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@(3)];
[subscriber sendCompleted];
return nil;
}];
RACSignal *concatSignal = [RACSignal concat:@[signal2, signal1, signal3]];
[concatSignal subscribeNext:^(id _Nullable x) {
// 分别输出2,1,3
NSLog(@"%@", x);
}];

then

用于连接两个信号,内部也是使用 concat,当前一个信号完成之后才会连接 then 返回的信号,但是会忽略前一个信号,只会触发下个信号。

then
RACSignal *signal1 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
[subscriber sendNext:@(1)];
[subscriber sendCompleted];
return nil;
}];
RACSignal *thenSignal = [signal1 then:^RACSignal * _Nonnull{
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@(2)];
[subscriber sendCompleted];
return nil;
}];
}];
[thenSignal subscribeNext:^(id _Nullable x) {
// 输出2
NSLog(@"%@", x);
}];

zip

将对各信号压缩成一个信号,只有当几个信号同时 sendNext 的时候才会触发压缩流的 next 事件,其中每一个信号 send 的内容都是一一对应的。

zip
RACSignal *signal1 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
[subscriber sendNext:@(1)];
// 不会覆盖上一个信号
[subscriber sendNext:@(3)];
return nil;
}];
RACSignal *signal2 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@(2)];
return nil;
}] ;
RACSignal *zipSignal = [RACSignal zip:@[signal1, signal2]];
[zipSignal subscribeNext:^(id _Nullable x) {
// 输出(1, 2)
NSLog(@"%@", x);
}];

combineLatest

将多个信号组合起来,当其中每一个信号都 sendNext 之后,才会触发组合的信号,其中每一个信号再次 sendNext 都会覆盖之前的信号内容,返回的是一个 RACTuple(元组,类似于NSArray)。

combineLatest
RACSignal *signal1 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
[subscriber sendNext:@(1)];
// 将覆盖之前的信号,这就是跟zip的区别
[subscriber sendNext:@(3)];
return nil;
}];
RACSignal *signal2 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@(2)];
return nil;
}];
//如果其中一个信号不sendNext,则不会触发组合信号
RACSignal *combineSignal = [RACSignal combineLatest:@[signal1, signal2]];
[combineSignal subscribeNext:^(id _Nullable x) {
// 输出(3, 2)
NSLog(@"%@", x);
}];

rac_liftSelector: withSignalsFromArray:

当信号组中每一个信号都至少一次 sendNext 之后,将触发 Selector 方法,类似于 combineLatest。

rac_liftSelector: withSignalsFromArray:
RACSubject *subject1 = [RACSubject subject];
RACSubject *subject2 = [RACSubject subject];
[[self rac_liftSelector:@selector(updateWithParameter1:parameter2:) withSignals:subject1, subject2, nil] subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
}];
[subject1 sendNext:@1];
[subject2 sendNext:@2];

reduceEach

一般用于元组,把元组的值聚合成一个值。

reduceEach
RACSignal *combineSignal = [RACSignal combineLatest:@[signal1, signal2]];
RACSignal *reduceSignal = [combineSignal reduceEach:^id (NSNumber *num1, NSNumber *num2){
return @(num1.doubleValue+num2.doubleValue);
}];
[reduceSignal subscribeNext:^(id _Nullable x) {
// 输出3
NSLog(@"%@", x);
}];
// 等同于:`+ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock`
// RACSignal *combineSignal = [RACSignal combineLatest:@[signal1,signal2] reduce:^id (NSNumber *num1, NSNumber *num2){
// return @(num1.doubleValue+num2.doubleValue);
// }];

filter

过滤信号,添加筛选条件,只有符合的才会触发调用。

filter
RACSignal *signal1 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
[subscriber sendNext:@(1)];
[subscriber sendNext:@"3"];
return nil;
}];
RACSignal *filterSignal = [signal1 filter:^BOOL(id _Nullable value) {
return [value isKindOfClass:[NSNumber class]];
}];
[filterSignal subscribeNext:^(id _Nullable x) {
// 输出1
NSLog(@"%@", x);
}];

// 数组的筛选
RACSequence *sequence = [@[@(1),@(2),@"3"].rac_sequence filter:^BOOL(id _Nullable value) {
return [value isKindOfClass:[NSNumber class]];
}];
[sequence.signal subscribeNext:^(id _Nullable x) {
// 输出1, 2
NSLog(@"%@", x);
}];

distinctUntilChanged

当前值跟上一次的值不同的时候,就会触发调用,否则被忽略。

distinctUntilChanged
[textField.rac_textSignal.distinctUntilChanged subscribeNext:^(NSString * _Nullable x) {
// 变化时输出变化之后的值
NSLog(@"%@", x);
}];

take

从第一个信号开始设置信号发送的有效的个数。

take
RACSignal *signal = [[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
[subscriber sendNext:@(1)];
[subscriber sendNext:@(2)];
return nil;
}] take:1];

[signal subscribeNext:^(id _Nullable x) {
// 输出1,因为take为1,所以有效的只有最开始的那一个,其他的忽略掉了
NSLog(@"%@", x);
}];

takeLast

从最后一个开始设置信号发送的有效个数,必须 sendCompleted,不然不知道总共多少个信号。

takeLast
[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
[subscriber sendNext:@(1)];
[subscriber sendNext:@(2)];
[subscriber sendCompleted];
return nil;
}] takeLast:1] subscribeNext:^(id _Nullable x) {
// 输出2
NSLog(@"%@", x);
}];

takeUntil

[signal1 takeUntil:signal2],当 signal2 已经 sendNext 或者 sendCompleted,signal1 就会失效。

takeUntil
RACSignal *signal1 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
[subscriber sendNext:@(1)];
return nil;
}];
RACSignal *signal2 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@(2)];
return nil;
}];
[[signal1 takeUntil:signal2] subscribeNext:^(id _Nullable x) {
// 什么都不会输出,因为signal2已经sendNext,所以signal1就会失效
NSLog(@"%@", x);
}];

skip

跳跃,从第一个发出的信号开始跳。

skip
[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
[subscriber sendNext:@(1)];
[subscriber sendNext:@(2)];
[subscriber sendNext:@(3)];
return nil;
}] skip:2] subscribeNext:^(id _Nullable x) {
// 输出3
NSLog(@"%@", x);
}];

doNext

在执行 sendNext 之前会执行这个。

doNext
[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
[subscriber sendNext:@(1)];
return nil;
}] doNext:^(id _Nullable x) {
x = [NSString stringWithFormat:@"%@haha",x];
// 输出1haha,在订阅回调之前执行
NSLog(@"%@", x);
}] subscribeNext:^(id _Nullable x) {
// 输出1
NSLog(@"%@", x);
}];

timeout

在超过设定时间范围之后让信号报错,且不能发送内容。

timeout
RACSubject *subject = [RACSubject subject];
[[subject timeout:3 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(id _Nullable x) {
// 只输出1
NSLog(@"%@", x);
} error:^(NSError * _Nullable error) {
// 3秒之后输出错误日志
NSLog(@"%@", error);
}];
[subject sendNext:@1];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subject sendNext:@2];
});

interval

定时,每隔一定时间发出时间信号。

interval
// RACScheduler:队列
[[RACSignal interval:1 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(NSDate * _Nullable x) {
// 每隔一秒输出当前时间
NSLog(@"%@", x);
}];

delay

延时发送信号。

delay
[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
[subscriber sendNext:@(1)];
return nil;
}] delay:3] subscribeNext:^(id _Nullable x) {
// 3秒之后输出1
NSLog(@"%@", x);
}];

retry

重试,只要失败,就会重新执行创建信号中的 block,直到成功。

retry
__block NSInteger i = 0;
[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
i++;
if (i > 10) {
[subscriber sendNext:@(1)];
}else{
[subscriber sendError:nil];
}
return nil;
}] retry] subscribeNext:^(id _Nullable x) {
// 重试10次之后输出1
NSLog(@"%@", x);
} error:^(NSError * _Nullable error) {
NSLog(@"%@", error);
}];

throttle

节流,当某个信号发送比较频繁的时候,可以限制在一定之间内不接受信号,等过了这个时间再取最后发送的信号内容发出,类似于 bufferWithTime:onScheduler:

throttle
RACSubject *subject = [RACSubject subject];
// [subject bufferWithTime:1 onScheduler:[RACScheduler currentScheduler]];
[[subject throttle:1] subscribeNext:^(id _Nullable x) {
// 输出3,拿到最后发出的内容3
NSLog(@"%@", x);
}];
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@3];

deliverOn

内容传递切换到指定线程中,副作用在原来线程中,把在创建信号时 block 中的代码称之为副作用。

subscribeOn

内容传递和副作用都会切换到指定线程中。

常用类

RACSubject

信号提供者,本身可以充当信号,又能发送信号,继承自 RACSignal,但是底层实现跟 RACSignal 有些不一样,当订阅信号的时候会创建订阅者并保存订阅响应 Block,而发送信号的时候会遍历订阅者,然后分别调用 nextBlock。它提供的 API 很少,但是经常使用,因为它继承自 RACSignal。这里顺便来看一下方法 flatten 跟 switchToLatest,这两个都只能用来处理信号中的信号。

flatten

压平信号中的信号,信号中的信号我们称之为子信号,flatten 可以拿到所有子信号发送的值。

flatten
RACSubject *subject = [RACSubject subject];
RACSubject *subSubject1 = [RACSubject subject];
RACSubject *subSubject2 = [RACSubject subject];
[subject subscribeNext:^(id _Nullable x) {
// 分别输出subSubject1,subSubject2,但是不能拿到其中的值
NSLog(@"%@", x);
}];
[subject.flatten subscribeNext:^(id _Nullable x) {
// 分别输出1, 2,flatten可以拿到所有子信号发送的值
NSLog(@"%@", x);
}];
[subject sendNext:subSubject1];
[subject sendNext:subSubject2];
[subSubject1 sendNext:@1];
[subSubject2 sendNext:@2];

switchToLatest

与 flatten 相同,压平信号中的信号,不同的是,在存在多个子信号时候只会拿到最新的子信号,然后输出最新的子信号的值。

switchToLatest
RACSubject *subject = [RACSubject subject];
RACSubject *subSubject1 = [RACSubject subject];
RACSubject *subSubject2 = [RACSubject subject];
[subject subscribeNext:^(id _Nullable x) {
// 分别输出subSubject1,subSubject2,但是不能拿到其中的值
NSLog(@"%@", x);
}];
[subject.switchToLatest subscribeNext:^(id _Nullable x) {
// 输出2,switchToLatest只会拿到最新的子信号发送的值
NSLog(@"%@", x);
}];
[subject sendNext:subSubject1];
[subject sendNext:subSubject2];
[subSubject1 sendNext:@1];
[subSubject2 sendNext:@2];

RACReplaySubject

重复提供信号类,继承自 RACSubject,它可以先发送信号,再订阅信号,原理就是将发送的信号内容保存了起来,当订阅信号的时候再将之前保存的信号,由订阅者一个一个的发送出来,而保存信号的容量由 capacity 来控制。

RACReplaySubject
RACReplaySubject *replaySubject = [RACReplaySubject replaySubjectWithCapacity:5];
[replaySubject subscribeNext:^(id _Nullable x) {
// 输出1
NSLog(@"%@", x);
}];
[replaySubject sendNext:@1];
[replaySubject subscribeNext:^(id _Nullable x) {
// 输出1
NSLog(@"%@", x);
}];

RACMulticastConnection

这是一个组播连接类,是对信号的一个封装处理,当一个信号被多次订阅时,则会多次执行 didSubscribe 这个 Block,造成副作用,而这个类就能避免多次执行 didSubscribe,是一对多的单向数据流,一般用来处理信号被多次订阅的情况。

RACMulticastConnection
__block int i = 0;
// 创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@(i)];
i ++;
return nil;
}];
//创建RACMulticastConnection对象
RACMulticastConnection *connect = [signal publish]
[connect.signal subscribeNext:^(id _Nullable x) {
// 输出0
NSLog(@"%@", x);
}];
[connect.signal subscribeNext:^(id _Nullable x) {
// 输出0,当再次订阅时,不会再执行didSubscribe,所以并没有i++
NSLog(@"%@", x);
}];
// 连接
[connect connect];

方法解析及实现原理:

  • publish,multicast:这是对RACMulticastConnection初始化方法的一个封装,publish其实就是调用了multicast,并把创建好的RACSubject对象传给它,而multicast也就是调用了RACMulticastConnection的初始化方法,将原始信号传给source,把RACSubject对象传给subject。
  • 当我们订阅connect.signal,其实就是订阅subject,然后将subject的订阅者保存起来,而调用[connect connect]的时候,会订阅原始信号(source),而source的订阅者就是subject,这时候subject就会执行[subject sendNext],之后就会遍历subject所有的订阅者,逐一发送信号,触发外部subscribeNext回调。

RACCommand

这是一个命令类,可以把事件如何处理,事件中的数据如何传递,包装到这个类中,他可以很方便的监控事件的执行过程,一般来说是在UI上的某些动作来触发这些事件,比如点击一个按钮,RACCommand的实例能够决定是否可以被执行,一般用于网络请求,监控请求过程。

  1. 创建命令,初始化RACCommand对象
  2. 在signalBlock中创建RACSignal,用来做数据传递,如果不需要可以创建空信号[RACSignal empty]。
  3. 执行命令execute
RACCommand
// 1. 创建命令对象
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
// 输出1,由execute传入
NSLog(@"%@", input);
// 2. 创建信号
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@(2)];
// 注意:数据传递完,最好调用sendCompleted,这时命令才执行完毕。
[subscriber sendCompleted];
return nil;
}];
}];

// 获取信号传输的数据
[command.executionSignals.switchToLatest subscribeNext:^(id x) {
// 输出2
NSLog(@"%@", x);
}];
// 这里用flatten跟switchToLatest也是一样的
[[command.executionSignals flatten] subscribeNext:^(id _Nullable x) {
// 输出2
NSLog(@"%@", x);
}];

// 监听命令是否执行完毕,初始化时会调用一次,用skip直接跳过。
[[command.executing skip:1] subscribeNext:^(id x) {
if ([x boolValue] == YES) {
// 正在执行
NSLog(@"正在执行");
}else{
// 执行完成
NSLog(@"执行完成");
}
}];

// 3. 执行命令
RACSignal *connectSignal = [command execute:@1] ;
[connectSignal subscribeNext:^(id _Nullable x) {
// 输出2,connectSignal是connect.signal
NSLog(@"%@", x);
}];

RACChannel

这是一个通道类,可以理解为一个双向的连接,连接的两端都配有RACChannelTerminal(通道终端,继承自RACSignal,且又实现了RACSubscriber协议,所以它可以充当信号,又能发送信号),分别是leadingTerminal,followingTerminal,只要其中任何一端输出信号,另一端都会有相同的信号输出。我们平时很少直接使用RACChannel,而是使用RACChannelTo。

RACChannel
RACChannelTerminal *followT = RACChannelTo(view, backgroundColor);
[followT subscribeNext:^(id _Nullable x) {
// 每点击一次就输出一次随机颜色
NSLog(@"%@", x);
}];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]init];
[tap.rac_gestureSignal subscribeNext:^(__kindof UIGestureRecognizer * _Nullable x) {
// 改变view.backgroundColor
[followT sendNext:RandomColor];
}];

// 将_textField.backgroundColor跟view.backgroundColor绑定
RACChannelTo(button, backgroundColor) = RACChannelTo(view, backgroundColor);

常用宏

RAC(TARGET, ...)

给某个对象的某个属性进行绑定。

RAC(TARGET, ...)
// 当textfield开始编辑时,关闭button响应
RAC(_button, enabled) = [[self rac_signalForSelector:@selector(textFieldDidBeginEditing:) fromProtocol:@protocol(UITextFieldDelegate)] mapReplace:@NO];

RACObserve(TARGET, KEYPATH)

KVO,监听某个对象的属性,返回的是信号。

RACObserve(TARGET, KEYPATH)
[RACObserve(self.view, backgroundColor) subscribeNext:^(id  _Nullable x) {
NSLog(@"%@", x);
}];

RACChannelTo

用于双向绑定的一个通道终端。

RACChannelTo
// 将_textField.backgroundColor跟view.backgroundColor绑定
RACChannelTo(button, backgroundColor) = RACChannelTo(view, backgroundColor);

RACTuplePack

将数据包装成 RACTuple(元组)。

RACTuplePack
RACTuple *tuple = RACTuplePack(@1, @2, @"3");

RACTupleUnpack(...)

把元组解包成对应的数据

RACTupleUnpack(...)
// 传入需要解析生成的变量名,从第一个开始解析
RACTupleUnpack(NSNumber *num1, NSNumber *num2) = tuple;
// 输出 1, 2
NSLog(@"%@, %@", num1, num2);