原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容
目录
- 一、ReactiveCocoa的使用
- 1、RACSignal
- 2、RACSubject
- 3、RACCommand
- 4、RACMulticastConnection
- 5、RACTuple和RACSequence
- 6、事件监听
- 7、宏
- 8、操作方法之映射
- 9、操作方法之组合
- 10、操作方法之过滤
- 11、操作方法之秩序
- 12、操作方法之线程
- 13、操作方法之时间
- 14、操作方法之重复
- 15、RAC双向绑定
- 二、ReactiveCocoa的源码解析
- 1、RACSignal
- 2、RACSubject
- 3、RACReplaySubject
- 4、RACMulticastConnection
- 5、RACCommand
- Demo
- 参考文献
十三、函数响应式编程
-
ReactiveCocoa: 函数响应式编程,搭配
MVVM
使用 -
ReactiveObjC:RAC提供
Signals
来捕获当前值和将来值 -
RxSwift:
Swift
函数响应式编程框架 - PromiseKit:提供了很多实用的异步函数
-
Hanson:
Swift
中的轻量级观察和绑定
一、ReactiveCocoa的使用
ReactiveCocoa
中的核心类是RACSignal
,其子类包括RACDynamicSignal
、RACReturnSignal
、RACEmptySignal
、RACErrorSignal
。用到的函数有:map take skip ignore filter
1、RACSignal
这是ReactiveCocoa
的核心类,用来表示数据传递,只要有数据变化,信号内部收到数据后,就会马上发出数据。
❶ 使用方法
// 创建信号
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
// 订阅信号
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
// 发送信号
- (void)sendNext:(id)value
// 这是信号的订阅者,本质是一个协议,遵守这个协议的类对象,并且实现协议方法就可以成为一个订阅者
RACSubscriber
// 这是用于取消订阅或者清理资源的类,当信号发送完成或者发送错误的时候会自动触发调用
RACDisposable
❷ 实现原理
- 创建信号,首先把
didSubscribe
保存到信号中,还不会触发。 - 当信号被订阅,也就是调用
signal
的subscribeNext:nextBlock
。 -
subscribeNext
内部会创建订阅者subscriber
,并且把nextBlock
保存到订阅者subscriber
中。 -
subscribeNext
内部会调用之前创建的信号siganl
的didSubscribe
。 -
siganl
的didSubscribe
中调用发送信号[subscriber sendNext:@1];
。 - 发送信号
sendNext
底层其实就是执行订阅者subscriber
的nextBlock
。
❸ Demo演示
- (void)RACSignalDemo
{
// 1.创建信号
RACSignal *single = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// block调用时刻:每当有订阅者订阅信号,就会调用block
NSLog(@"想");
// 2.发送信号
[subscriber sendNext:@"发送了信号"];
NSLog(@"你");
// 如果不再发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号
[subscriber sendCompleted];
// 执行完信号后进行的清理工作,如果不需要就返回 nil
return [RACDisposable disposableWithBlock:^{
// block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号
// 执行完Block后,当前信号就不再被订阅了
NSLog(@"豆腐");
}];
}];
NSLog(@"我");
// 3.订阅信号,才会激活信号
[single subscribeNext:^(id x) {
// block调用时刻:每当有信号发出数据,就会调用block
NSLog(@"吃");
NSLog(@"信号的值:%@",x);
}];
}
输出结果为:
2020-09-16 10:17:41.359920+0800 框架Demo[54921:9299537] 我
2020-09-16 10:17:41.361400+0800 框架Demo[54921:9299537] 想
2020-09-16 10:17:41.361493+0800 框架Demo[54921:9299537] 吃
2020-09-16 10:17:41.361559+0800 框架Demo[54921:9299537] 信号的值:发送了信号
2020-09-16 10:17:41.361627+0800 框架Demo[54921:9299537] 你
2020-09-16 10:17:41.361712+0800 框架Demo[54921:9299537] 豆腐
没错,输出:我 想 吃 你 豆腐
。哈哈,我很喜欢这个Demo
,因为信号的运行流程描述得非常形象。
❹ 流程解释
-
createSignal
方法是创建信号,创建好的信号,没有被订阅前,只是冷信号,此时是不会走createSignal
后面的block
的。程序往下,就走到“NSLog(@"我")”
。 - 然后走到
subscribeNext
,这一步就是订阅信号,订阅号信号后,信号single
就变成了热信号。 - 既然变成热信号,就开始走
createSignal
后面的block
,所以就打印出了“NSLog(@"想")”
。 - 下面是
sendNext
,即发送信号,发送了信号,订阅者就会收到信号,发送的内容可以从订阅信号subscribeNext
后面的block
中获取到,程序就走到subscribeNext
后面的block
中,所以就打印了“NSLog(@"吃")”
。 - 当订阅信号的
subscribeNext
后面的block
走完以后,程序又回到createSignal
后面的block
中,继续未完成的代码,所以就打印“NSLog(@"你")”
,继续往下就是[subscriber sendCompleted]
,这句代码的意思是,发送完成了,订阅自动移除,没有了订阅者了,信号又变成了冷信号。 - 接下来就是
return
,返回一个RACDisposable
对象,这个的作用就是,可以用来手动移除订阅。RACDisposable
对象,创建完成,就走进创建方法的block
中,也就是打印NSLog(@"豆腐")
。
❺ RACDisposable 手动删除订阅的使用
- (void)RACDisposableDemo
{
// 1.创建信号
RACSignal * single = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"想");
// 2.发送信号
[subscriber sendNext:@"发送了一个信号"];
NSLog(@"你");
//RACDisposable 手动移除订阅者
return [RACDisposable disposableWithBlock:^{
NSLog(@"豆腐");
}];
}];
// 3.订阅信号
NSLog(@"我");
RACDisposable * disposable = [single subscribeNext:^(id x) {
NSLog(@"吃");
NSLog(@"信号的值:%@",x);
}];
// 4.手动移除订阅
[disposable dispose];
}
输出结果为:
2020-09-16 10:28:15.616011+0800 框架Demo[55046:9308956] 我
2020-09-16 10:28:15.617600+0800 框架Demo[55046:9308956] 想
2020-09-16 10:28:15.617697+0800 框架Demo[55046:9308956] 吃
2020-09-16 10:28:15.617768+0800 框架Demo[55046:9308956] 信号的值:发送了一个信号
2020-09-16 10:28:15.617837+0800 框架Demo[55046:9308956] 你
2020-09-16 10:28:15.617935+0800 框架Demo[55046:9308956] 豆腐
两份代码不同之处是,删去了自动移除订阅[subscriber sendCompleted]
,添加了手动删除订阅[disposable dispose]
。
不过手动删除用的少。那既然用得少,我们还是用自动删除吧,优化下,返回nil
就可以了。
- (RACSignal *)autoDeleteSubscription
{
// 1.创建信号
RACSignal *single = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"想");
// 2.发送信号
[subscriber sendNext:@"发送了信号"];
NSLog(@"你");
// 4.发送完成,订阅自动移除
[subscriber sendCompleted];
// RACDisposable 手动移除订阅者
return nil;
}];
NSLog(@"我");
// 3.订阅信号
[single subscribeNext:^(id x) {
NSLog(@"吃");
NSLog(@"信号的值:%@",x);
}];
return single;
}
输出结果为:
2020-09-16 10:33:16.676630+0800 框架Demo[55133:9313860] 我
2020-09-16 10:33:16.678199+0800 框架Demo[55133:9313860] 想
2020-09-16 10:33:16.678296+0800 框架Demo[55133:9313860] 吃
2020-09-16 10:33:16.678368+0800 框架Demo[55133:9313860] 信号的值:发送了信号
2020-09-16 10:33:16.678436+0800 框架Demo[55133:9313860] 你
2、RACSubject
这是信号的提供者,被称为信号的信号,即既可以自己充当信号,也能够发送信号,常用来代替代理。
❶ 使用方法
// 创建信号
[RACSubject subject]
// 订阅信号
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
// 发送信号
sendNext:(id)value
❷ Demo演示
// RACSubject的使用
- (void)RACSubjectDemo
{
// 1.创建信号
RACSubject *subject = [RACSubject subject];
// 2.订阅信号
[subject subscribeNext:^(id x) {
// block调用时刻:当信号发出新值,就会调用
NSLog(@"第一个订阅者:%@",x);
}];
[subject subscribeNext:^(id x) {
// block调用时刻:当信号发出新值,就会调用
NSLog(@"第二个订阅者:%@",x);
}];
// 3.发送信号
[subject sendNext:@"谢佳培"];
}
输出结果为:
2020-09-16 10:42:03.537529+0800 框架Demo[55263:9321680] 第一个订阅者:谢佳培
2020-09-16 10:42:03.537649+0800 框架Demo[55263:9321680] 第二个订阅者:谢佳培
❸ 实现原理
RACSubject
底层实现和RACSignal
不一样。
- 调用
subscribeNext
订阅信号,只是把订阅者保存起来,并且订阅者的nextBlock
已经赋值了。 - 调用
sendNext
发送信号,遍历刚刚保存的所有订阅者,一个接一个调用订阅者的nextBlock
。
❹ 替换代理
需求
- 给第一个控制器添加一个按钮,用来跳转到第二个控制器界面。
- 在第二个控制器上也添加个按钮,点击按钮,通知第一个控制器。
第一个控制器
@interface FirstViewController : UIViewController
@end
@implementation FirstViewController
- (void)viewDidLoad
{
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(120, 300, 200, 100)];
button.backgroundColor = [UIColor blackColor];
[button setTitle:@"跳转到第二个控制器" forState:UIControlStateNormal];
[button addTarget:self action:@selector(butttonClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
- (void)butttonClick
{
// 1.创建第二个控制器
SecondViewController *secondViewController = [[SecondViewController alloc] init];
// 2.设置代理信号
secondViewController.delegateSignal = [RACSubject subject];
// 3.订阅代理信号
[secondViewController.delegateSignal subscribeNext:^(id x) {
NSLog(@"点击了通知按钮");
}];
// 4.跳转到第二个控制器
[self presentViewController:secondViewController animated:YES completion:nil];
}
SecondViewController
@interface SecondViewController : UIViewController
/** 添加一个RACSubject代替代理 */
@property (nonatomic, strong) RACSubject *delegateSignal;
@end
@implementation SecondViewController
- (void)viewDidLoad
{
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(120, 500, 200, 100)];
button.backgroundColor = [UIColor blackColor];
[button setTitle:@"通知第一个控制器" forState:UIControlStateNormal];
[button addTarget:self action:@selector(notice) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
// 监听第二个控制器按钮是否被点击,如果被点击了则通知第一个控制器
- (void)notice
{
// 判断代理信号是否有值
if (self.delegateSignal)
{
// 有值,才需要通知
[self.delegateSignal sendNext:nil];
}
}
@end
输出结果为:
2020-09-16 11:07:36.287531+0800 框架Demo[55772:9350854] 点击了通知按钮
❺ RACRelaySubject
用途
继承自RACSubject
,特点是 订阅信号 和 发送信号 没有先后顺序,而RACSubject
就必须先订阅后发送。常用来解决创建信号的block
会带来副作用的场合,以及限制缓存值的数量。
使用
- (void)RACRelaySubjectDemo
{
// 1.创建信号
RACReplaySubject *replaySubject = [RACReplaySubject subject];
// 2.发送信号
[replaySubject sendNext:@1];
[replaySubject sendNext:@2];
// 3.订阅信号
[replaySubject subscribeNext:^(id x) {
NSLog(@"第一个订阅者接收到的数据为:%@",x);
}];
// 订阅信号
[replaySubject subscribeNext:^(id x) {
NSLog(@"第二个订阅者接收到的数据为:%@",x);
}];
}
输出结果为:
2020-09-16 11:25:27.733886+0800 框架Demo[55997:9363518] 第一个订阅者接收到的数据为:1
2020-09-16 11:25:27.734014+0800 框架Demo[55997:9363518] 第一个订阅者接收到的数据为:2
2020-09-16 11:25:27.734763+0800 框架Demo[55997:9363518] 第二个订阅者接收到的数据为:1
2020-09-16 11:25:27.734913+0800 框架Demo[55997:9363518] 第二个订阅者接收到的数据为:2
原理
RACReplaySubject
底层实现和RACSubject
不一样。
- 调用
sendNext
发送信号,把值保存起来,然后遍历刚刚保存的所有订阅者,一个接一个调用订阅者的nextBlock
。 - 调用
subscribeNext
订阅信号,遍历保存的所有值,一个接一个调用订阅者的nextBlock
。
3、RACCommand
用于处理各种事件的类,可以把事件如何处理和数据如何传递,封装到这个类中,方便监控事件的执行过程,常用来处理网络请求场景
❶ 使用方法
// 创建signalBlock,并返回信号
initWithSignalBlock:(RACSignal * (^)(id input))signalBlock
// 执行命令
- (RACSignal *)execute:(id)input
❷ Demo演示
@property (nonatomic, strong) RACCommand *command;
- (void)RACCommandDemo
{
// 1.创建命令
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
NSLog(@"执行命令");
// 创建空信号。必须返回信号
// return [RACSignal empty];
// 2.创建信号,用来传递数据
return [RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@"请求接口返回的数据"];
// 数据传递完成,调用sendCompleted,这时命令才执行完毕
[subscriber sendCompleted];
return nil;
}];
}];
// 强引用命令,不要被销毁,否则接收不到数据
self.command = command;
// 3.订阅RACCommand中的信号
[command.executionSignals subscribeNext:^(id x) {
[x subscribeNext:^(id x) {
NSLog(@"订阅RACCommand中的信号:%@",x);
}];
}];
// RAC高级用法
// switchToLatest:获取signal of signals发出的最新信号,也就是可以直接拿到RACCommand中的信号
[command.executionSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"获取signal of signals发出的最新信号:%@",x);
}];
// 4.监听命令是否执行完毕,默认会来一次,可以直接跳过,skip表示跳过第一次信号
[[command.executing skip:1] subscribeNext:^(id x) {
if ([x boolValue] == YES)// 正在执行
{
NSLog(@"正在执行");
}
else// 执行完成
{
NSLog(@"执行完成");
}
}];
// 5.执行命令
[self.command execute:@1];
}
输出结果为:
2020-09-16 11:38:51.237267+0800 框架Demo[56159:9372910] 执行命令
2020-09-16 11:38:51.240593+0800 框架Demo[56159:9372910] 正在执行
2020-09-16 11:38:51.240839+0800 框架Demo[56159:9372910] 订阅RACCommand中的信号:请求接口返回的数据
2020-09-16 11:38:51.240923+0800 框架Demo[56159:9372910] 获取signal of signals发出的最新信号:请求接口返回的数据
2020-09-16 11:38:51.241238+0800 框架Demo[56159:9372910] 执行完成
❸ 注意事项
-
signalBlock
必须要返回一个信号,不能传nil
- 如果不想要传递信号,直接创建空的信号
[RACSignal empty];
-
RACCommand
中信号如果数据传递完,必须调用[subscriber sendCompleted]
,这时命令才会执行完毕,否则永远处于执行中。 -
RACCommand
需要被强引用,否则接收不到RACCommand
中的信号,因此RACCommand
中的信号是延迟发送的。
❹ 设计思想
signalBlock
为什么要返回一个信号,这个信号有什么用?
在实际开发中,通常会把网络请求封装到RACCommand
,直接执行某个RACCommand
就能发送请求。当RACCommand
内部请求到数据的时候,需要把请求的数据传递给外界,这时候就需要通过signalBlock
返回的信号传递了。
如何拿到RACCommand
中返回信号发出的数据?
RACCommand
有个执行信号源executionSignals
,这个是signal of signals
(信号的信号),意思是信号发出的数据是信号,不是普通的类型。订阅executionSignals
就能拿到RACCommand
中返回的信号,然后订阅signalBlock
返回的信号,就能获取发出的值。
4、RACMulticastConnection
用于当一个信号被多次订阅时,为了保证创建信号时避免多次调用创建信号中的block
造成副作用,可以使用这个类处理,即RACMulticastConnection
用来解决重复请求问题。
❶ 使用方法
// 创建信号
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
// 创建连接
RACMulticastConnection *connect = [signal publish];
// 订阅信号
[connect.signal subscribeNext:nextBlock]
// 连接发送信号
[connect connect]
❷ Demo演示
- (void)RACMulticastConnectionDemo
{
// 1.创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
NSLog(@"发送请求");
[subscriber sendNext:@1];
return nil;
}];
// 2.创建连接
RACMulticastConnection *connect = [signal publish];
// 3.订阅信号
// 没有激活信号,只是保存订阅者到数组,必须通过调用连接来一次性调用所有订阅者的sendNext:
[connect.signal subscribeNext:^(id x) {
NSLog(@"订阅者一的信号");
}];
[connect.signal subscribeNext:^(id x) {
NSLog(@"订阅者二的信号");
}];
// 4.连接,激活信号
[connect connect];
}
输出结果为:
2020-09-16 13:47:33.666865+0800 框架Demo[57362:9432620] 发送请求
2020-09-16 13:47:33.667009+0800 框架Demo[57362:9432620] 订阅者一的信号
2020-09-16 13:47:33.667090+0800 框架Demo[57362:9432620] 订阅者二的信号
❸ 实现原理
- 创建
connect
,connect.sourceSignal
->RACSignal
(原始信号),connect.signal
->RACSubject
。 - 订阅
connect.signal
,会调用RACSubject
的subscribeNext
,创建订阅者,而且把订阅者保存起来,不会执行block
。 -
[connect connect]
内部会订阅RACSignal
(原始信号),并且订阅者是RACSubject
。 - 订阅原始信号,就会调用原始信号中的
didSubscribe
-
didSubscribe
,拿到订阅者调用sendNext
,其实是调用RACSubject
的sendNext
-
RACSubject
的sendNext
会遍历RACSubject
所有订阅者发送信号。 - 因为刚刚第二步都是在订阅
RACSubject
,因此会拿到第二步所有的订阅者,调用他们的nextBlock
。
5、RACTuple和RACSequence
RACTuple:元组类,类似NSArray
,在解构对象中经常使用。
RACSequence:集合类,使用它来快速遍历数组和字典。
- (void)RACTupleAndRACSequenceDemo
{
// 1.遍历数组
NSArray *numbers = @[@1,@2,@3,@4];
// numbers.rac_sequence:把数组转换成集合RACSequence
// numbers.rac_sequence.signal:把集合RACSequence转换RACSignal信号类
// 订阅信号,激活信号,会自动把集合中的所有值,遍历出来
[numbers.rac_sequence.signal subscribeNext:^(id x) {
NSLog(@"遍历数组:%@",x);
}];
// 2.遍历字典。遍历出来的键值对会包装成RACTuple(元组对象)
NSDictionary *dict = @{@"name":@"谢佳培",@"birth":@1997};
[dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
// 解包元组,会把元组的值按顺序给参数里面的变量赋值
RACTupleUnpack(NSString *key,NSString *value) = x;
NSLog(@"key:%@,value:%@",key,value);
// 相当于以下写法
NSString *keyStr = x[0];
NSString *valueStr = x[1];
NSLog(@"keyStr:%@,valueStr:%@",keyStr,valueStr);
}];
}
输出结果为:
2020-09-16 14:05:35.894392+0800 框架Demo[57599:9447492] 遍历数组:1
2020-09-16 14:05:35.894627+0800 框架Demo[57599:9447492] 遍历数组:2
2020-09-16 14:05:35.894746+0800 框架Demo[57599:9447492] 遍历数组:3
2020-09-16 14:05:35.894845+0800 框架Demo[57599:9447492] 遍历数组:4
2020-09-16 14:05:35.895003+0800 框架Demo[57599:9447492] 遍历数组:5
2020-09-16 14:05:35.895307+0800 框架Demo[57599:9447491] key:name,value:谢佳培
2020-09-16 14:05:35.895384+0800 框架Demo[57599:9447491] keyStr:name,valueStr:谢佳培
2020-09-16 14:05:35.895513+0800 框架Demo[57599:9447491] key:birth,value:1997
2020-09-16 14:05:35.895582+0800 框架Demo[57599:9447491] keyStr:birth,valueStr:1997
6、事件监听
输出结果为:
2020-09-16 16:37:31.678816+0800 框架Demo[59664:9563723] 用于更新UI的数据:firstData_发送请求1,secondData_发送请求2
2020-09-16 16:53:20.627209+0800 框架Demo[59999:9584933] 蒙奇 D 路飞
2020-09-16 16:37:52.625954+0800 框架Demo[59664:9563723] 文字改变了1
2020-09-16 16:37:52.701440+0800 框架Demo[59664:9563723] 文字改变了12
2020-09-16 16:37:52.870946+0800 框架Demo[59664:9563723] 文字改变了123
2020-09-16 16:37:47.692000+0800 框架Demo[59664:9563723] 按钮被点击了
2020-09-16 16:39:37.193890+0800 框架Demo[59664:9563723] 键盘弹出
2020-09-16 16:54:31.237834+0800 框架Demo[59999:9584933] 新值为:<RACTwoTuple: 0x600000830440> (
"NSPoint: {0, -0.66666666666666674}",
{
kind = 1;
new = "NSPoint: {0, -0.66666666666666674}";
}
)
❶ 代替代理:rac_signalForSelector
以前需要遵守代理协议,赋值delegate
,实现代理方法等,如果用rac_signalForSelector
则都不需要。
[[self rac_signalForSelector:@selector(viewDidAppear:)] subscribeNext:^(id x) {
NSLog(@"蒙奇 D 路飞");
}];
❷ 代替KVO:rac_valuesAndChangesForKeyPath
UIScrollView *scrolView = [[UIScrollView alloc] initWithFrame:CGRectMake(200, 300, 200, 200)];
scrolView.contentSize = CGSizeMake(200, 400);
scrolView.backgroundColor = [UIColor greenColor];
[self.view addSubview:scrolView];
[[scrolView rac_valuesAndChangesForKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew observer:self] subscribeNext:^(id x) {
NSLog(@"新值为:%@",x);
}];
❸ 监听事件:rac_signalForControlEvents
[[self.eventButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
NSLog(@"按钮被点击了");
}];
❹ 代替通知:rac_addObserverForName
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
NSLog(@"键盘弹出");
}];
❺ 监听文本框文字改变:rac_textSignal
使用RACSignal
创建信号,其实文本框中文字改变也是信号,按钮点击也是信号,RAC
为UITxtField
和UIButton
创建categary
,并做好了封装,直接就可以调用它们的信号。因为textField
的信号肯定是NSString
类型的,所以可以将id x
修改为NSString* x
。当你在文本框输入时,系统就会捕捉输入内容将其在控制台打印。
[self.eventTextField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"文字改变了%@",x);
}];
❻ 同步信号:rac_liftSelector:withSignalsFromArray:Signals
RACSignal *firstRequest = [RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@"发送请求1"];
return nil;
}];
RACSignal *secondRequest = [RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@"发送请求2"];
return nil;
}];
// 同步信号。每个参数对应信号发出的数据
[self rac_liftSelector:@selector(updateUIWithFirstRequestData:secondRequestData:) withSignalsFromArray:@[firstRequest,secondRequest]];
- (void)updateUIWithFirstRequestData:(id)firstData secondRequestData:(id)secondData
{
NSLog(@"用于更新UI的数据:firstData_%@,secondData_%@",firstData,secondData);
}
7、宏
❶ 宏RAC
提供了一个宏"RAC
(对象,属性)"来简化代码并增强可读性。RAC
宏有两个参数,一个是需要设置的对象,一个是设置的属性。
// 只要文本框文字改变,就会修改label的文字
RAC(self.macroLabel,text) = self.macroTextField.rac_textSignal;
// 文本框文字长度大于4为红色背景,小于4为蓝色背景,textView同理
RAC(self.macroTextField ,backgroundColor) = [self.macroTextField.rac_textSignal map:^id(NSString* value) {
return value.length > 4 ? [UIColor redColor]:[UIColor blueColor];
}];

❷ 宏RACObserve
宏RACObserve
用来监听某个对象的某个属性,返回的是信号。
// view.center的值
[RACObserve(self.view, center) subscribeNext:^(id x) {
NSLog(@"view.center的值为:%@",x);
}];
输出结果为:
2020-09-16 17:17:15.679022+0800 框架Demo[60348:9606184] view.center的值为:NSPoint: {207, 448}
❸ @weakify(Obj) 和 strongify(Obj):处理闭包强引用
RAC
的所有方法中,大部分是block
,所以无法避免在使用过程中导致循环引用,以前的解决办法是这样的:
- (void)weakifyDemo
{
__weak typeof(self) weakSelf;
[[[self.weakifyButton rac_signalForControlEvents:(UIControlEventTouchUpInside)]
doNext:^(id x) {
weakSelf.weakifyLabel.textColor = [UIColor redColor];
}] subscribeNext:^(UIControl *x) {
weakSelf.weakifyLabel.text = @"1314";
}];
}
如果每个block
都写的话,会很费劲,因为block
太多了,还好RAC
提供了两个宏,@weakify
和@strongify
:
- (void)weakifyDemo
{
@weakify(self);
[[[self.weakifyButton rac_signalForControlEvents:(UIControlEventTouchUpInside)]
doNext:^(id x) {
@strongify(self);
self.weakifyLabel.textColor = [UIColor redColor];
}] subscribeNext:^(UIControl *x) {
self.weakifyLabel.text = @"1314";
}];
}
@weakify
宏让你创建一个弱引用的影子对象(如果你需要多个弱引用,你可以传入多个变量),@strongify
让你创建一个对之前传入@weakify
对象的强引用。这样就解决了循环引用的问题。
❹ RACTuple 和 RACTupleUnpack
元组的构造与解构。
// 把参数中的数据包装成元组
RACTuple *tuple = RACTuplePack(@"谢佳培",@1997);
// 解包元组,会把元组的值,按顺序给参数里面的变量赋值
RACTupleUnpack(NSString *name,NSNumber *birth) = tuple;
NSLog(@"name:%@,birth:%@",name,birth);
输出结果为:
2020-09-16 17:24:45.740230+0800 框架Demo[60442:9612778] name:谢佳培,birth:1997
8、操作方法之映射
❶ flattenMap
作用
可以把源信号的内容映射成一个新的信号,信号可以是任意类型。
使用方法
- 传入一个
block
[返回值是RACSignal
,参数是value
]。 - 参数
value
就是源信号的内容,在block
内拿到源信号的内容做处理。 - 包装成
RACReturnSignal
信号作为返回值。
底层实现
-
flattenMap
内部调用bind
方法,flattenMap
中block
的返回值会作为bind
中bindBlock
的返回值。 - 当订阅绑定信号,就会生成
bindBlock
。 - 当源信号发送内容,就会调用
bindBlock(value, *stop)
。 - 调用
bindBlock
,内部就会调用flattenMap
的block
,flattenMap
的block
作用:就是把处理好的数据包装成信号。 - 返回的信号最终会作为
bindBlock
中的返回信号,当做bindBlock
的返回信号。 - 订阅
bindBlock
的返回信号,就会拿到绑定信号的订阅者,把处理完成的信号内容发送出来。
Demo演示
- (void)flattenMapDemo
{
// 创建源信号
RACSubject *subject = [RACSubject subject];
// 绑定信号
RACSignal *bindSignal = [subject flattenMap:^RACSignal *(id value) {
// block:只要源信号发送内容就会调用
// value:就是源信号发送的内容
NSString *str = [NSString stringWithFormat:@"%@",value];
NSLog(@"源信号发送的内容为:%@",str);
return [RACReturnSignal return:value];
}];
// 订阅绑定信号。flattenMap中返回的是什么信号,订阅的就是什么信号
[bindSignal subscribeNext:^(id x) {
NSLog(@"订阅的绑定信号收到的内容为:%@",x);
}];
// 源信号发送数据
[subject sendNext:@"哪吒和孙悟空"];
}
输出结果为:
2020-09-16 17:51:20.331132+0800 框架Demo[60815:9633081] 源信号发送的内容为:哪吒和孙悟空
2020-09-16 17:51:20.331289+0800 框架Demo[60815:9633081] 订阅的绑定信号收到的内容为:哪吒和孙悟空
❷ Map
作用
把源信号的值映射成一个新的值,返回一个对象,就是将一种信号转换成你想要的另一种信号。
使用方法
- 传入一个
block
[返回值是RACSignal
,参数是value
]。 -
value
就是源信号的内容,直接拿到源信号的内容做处理 - 把处理好的内容,直接返回就好了,不用包装成信号,返回的值,就是映射的值。
底层实现
-
Map
底层其实是调用flatternMap
,Map
中block
中的返回的值会作为flatternMap
中block
中的值。 - 当订阅绑定信号,就会生成
bindBlock
。 - 当源信号发送内容,就会调用
bindBlock(value, *stop)
。 - 调用
bindBlock
,内部就会调用flattenMap
的block
。 -
flattenMap
的block
内部会调用Map
中的block
,把Map
中的block
返回的内容包装成返回的信号。 - 返回的信号最终会作为
bindBlock
中的返回信号,当做bindBlock
的返回信号。 - 订阅
bindBlock
的返回信号,就会拿到绑定信号的订阅者,把处理完成的信号内容发送。
Demo演示
- (void)mapDemo
{
RACSubject *subject = [RACSubject subject];
RACSignal *bindSignal = [subject map:^id(id value) {
// 返回的类型,就是你需要映射的值
return [NSString stringWithFormat:@"%@",value];
}];
[bindSignal subscribeNext:^(id x) {
NSLog(@"神话英雄:%@",x);
}];
[subject sendNext:@"哪吒"];
[subject sendNext:@"悟空"];
}
输出结果为:
2020-09-16 18:06:47.150435+0800 框架Demo[61008:9645534] 神话英雄:哪吒
2020-09-16 18:06:47.150610+0800 框架Demo[61008:9645534] 神话英雄:悟空
如果想当文本框中输入的文字长度大于4的时候,改变文本框的背景色,一种方法是把过滤器的条件设置为4,然后在subscribeNext
的block
中直接给textField.backgroundColor
赋值。不过RAC
有转换信号的方法---map
,如下
- (void)mapColorDemo
{
[[[self.mapTextField.rac_textSignal filter:^BOOL(NSString* value) {
return value.length > 3 ? YES:NO;
}] map:^id(NSString* value) {
return value.length > 4 ? [UIColor redColor] : [UIColor blackColor];
}] subscribeNext:^(UIColor* value) {
self.mapTextField.backgroundColor = value;
}];
}
上面的代码的意思是,当输入的字符串长度超过3,就将字符串信号转换成颜色信号,然后订阅该颜色信号,并将颜色赋值给textField
的背景色。这样的话,当字符串大于4,文本框的背景色变成了红色。

9、操作方法之组合
❶ concat
作用
按一定顺序拼接信号,当多个信号发出的时候,有顺序的接收信号。
底层实现
- 当拼接信号被订阅,就会调用拼接信号的
didSubscribe
。 -
didSubscribe
中,会先订阅第一个源信号signalA
。 - 会执行第一个源信号
signalA
的didSubscribe
。 - 在第一个源信号
signalA
的didSubscribe
中发送值,就会调用第一个源信号signalA
订阅者的nextBlock
,
通过拼接信号的订阅者把值发送出来。 - 在第一个源信号
signalA
的didSubscribe
中发送完成,就会调用第一个源信号signalA
订阅者的completedBlock
,订阅第二个源信号signalB
这时候才激活signalB
。 - 订阅第二个源信号
signalB
,执行第二个源信号signalB
的didSubscribe
。 - 在第二个源信号
signalA
的didSubscribe中发送值,就会通过拼接信号的订阅者把值发送出来。
Demo演示
- (void)concatDemo
{
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
// 发送信号
[subscriber sendNext:@"山川"];
// 第一个信号必须要调用sendCompleted
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
// 发送信号
[subscriber sendNext:@"河流"];
return nil;
}];
// 创建组合信号
// concat: 按顺序去连接
RACSignal *concatSignal = [signalA concat:signalB];
// 订阅组合信号
[concatSignal subscribeNext:^(id x) {
// 既能拿到A信号的值,又能拿到B信号的值
NSLog(@"组合信号的值:%@",x);
}];
}
输出结果为:
2020-09-16 18:25:03.475250+0800 框架Demo[61256:9660054] 组合信号的值:山川
2020-09-16 18:25:03.475394+0800 框架Demo[61256:9660054] 组合信号的值:河流
❷ then
作用
then
方法会等待前面的信号中completed
事件的发送完成,然后再订阅由then block
返回的信号。这样就高效地把控制权从一个signal
传递给下一个。
底层实现
- 先过滤掉之前的信号发出的值。
- 使用
concat
连接then
返回的信号。
Demo演示
- (void)thenDemo
{
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
// 发送信号
[subscriber sendNext:@"山川"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
// 发送信号
[subscriber sendNext:@"河流"];
return nil;
}];
// 创建组合信号
// then: 忽略掉第一个信号所有值
RACSignal *thenSignal = [signalA then:^RACSignal *{
// 返回信号就是需要组合的信号
return signalB;
}];
// 订阅信号
[thenSignal subscribeNext:^(id x) {
NSLog(@"组合信号值:%@",x);
}];
}
输出结果为:
2020-09-16 18:25:32.523399+0800 框架Demo[61269:9661234] 组合信号值:河流
❸ merge
作用
把多个信号合并为一个信号,任何一个信号有新值的时候就会调用。
底层实现
- 合并信号被订阅的时候,就会遍历所有信号,并且发出这些信号。
- 每发出一个信号,这个信号就会被订阅
- 也就是合并信号一被订阅,就会订阅里面所有的信号。
- 只要有一个信号被发出就会被监听。
Demo演示
- (void)mergeDemo
{
// 任意一个信号请求完成都会订阅到
RACSubject *signalA = [RACSubject subject];
RACSubject *signalB = [RACSubject subject];
// 组合信号
RACSignal *mergeSignal = [signalA merge:signalB];
// 订阅信号
[mergeSignal subscribeNext:^(id x) {
// 任意一个信号发送内容都会来这个block
NSLog(@"信号值:%@",x);
}];
// 发送数据
[signalA sendNext:@"abc"];
[signalB sendNext:@"123"];
}
输出结果为:
2020-09-22 16:06:18.942371+0800 框架Demo[82453:17075493] 信号值:abc
2020-09-22 16:06:18.942508+0800 框架Demo[82453:17075493] 信号值:123
❹ zipWith
作用
把两个信号压缩成一个信号,只有当两个信号同时发出信号内容时,并且把两个信号的内容合并成一个元组,才会触发压缩流的next
事件。
底层实现
- 定义压缩信号,内部就会自动订阅
signalA
,signalB
。 - 每当
signalA
或者signalB
发出信号,就会判断signalA
,signalB
有没有发出个信号,有就会把最近发出的信号都包装成元组发出。
Demo演示
- (void)zipWithDemo
{
RACSubject *signalA = [RACSubject subject];
RACSubject *signalB = [RACSubject subject];
// 压缩成一个信号,等所有信号都发送内容的时候才会调用
// 当一个界面多个请求的时候,要等所有请求完成才能更新UI
RACSignal *zipSignal = [signalA zipWith:signalB];
// 订阅信号
[zipSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 发送信号
// 打印顺序是由压缩的信号顺序决定的,不是由发送信号的顺序决定的
[signalB sendNext:@"123"];
[signalA sendNext:@"abc"];
}
输出结果为:
2020-09-22 16:14:54.680677+0800 框架Demo[82572:17084745] 信号值为:<RACTwoTuple: 0x6000031c0280> (
abc,
123
)
❺ combine
作用
把两个信号组合成一个信号,跟zip
一样,没什么区别。
底层实现
- 当组合信号被订阅,内部会自动订阅
signalA
,signalB
,必须两个信号都发出内容,才会被触发。 - 并且把两个信号组合成元组发出。
Demo演示
- (void)combineDemo
{
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@1];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@2];
return nil;
}];
// 把两个信号组合成一个信号,跟zip一样,没什么区别
RACSignal *combineSignal = [signalA combineLatestWith:signalB];
[combineSignal subscribeNext:^(id x) {
NSLog(@"信号值为:%@",x);
}];
}
输出结果为:
2020-09-22 16:20:38.777686+0800 框架Demo[82657:17091255] 信号值为:<RACTwoTuple: 0x6000035d0260> (
1,
2
)
想象一下,如果当textField
和textView
同时满足某个条件时,才能进行某项操作的话,应该如何写呢?RAC
为我们准备了一个方法——combineLatest:reduce:
信号合并。
- (void)combineTextFieldAndTextViewDemo
{
RACSignal *mergeTwoSignal = [RACSignal combineLatest:@[self.combineTextField.rac_textSignal, self.combineTextView.rac_textSignal] reduce:^id(NSString * value1,NSString * value2) {
return [NSNumber numberWithBool:([value1 isEqualToString:@"11111"] && [value2 isEqualToString:@"22222"])];
}];
RAC(self.combineButton, enabled) = [mergeTwoSignal map:^id(NSNumber* value) {
return value;
}];
}
- (void)clickCombineButton
{
NSLog(@"齐天大圣");
}
上面的代码的意思是,当textField
中的文字为"11111"
,同时textView
中的文字为"22222"
的时候,返回一个信号,信号的类型是NSNumber
,然后通过转换器map
,将值返回,返回的值用于确定按钮是否可用。

10、操作方法之过滤
❶ filter
作用
使用它可以获取满足条件的信号。
Demo演示
- (void)filterDemo
{
// 只有当我们文本框的内容长度大于5,才想要获取文本框的内容
[[self.filterTextField.rac_textSignal filter:^BOOL(id value) {
// value: 源信号的内容
return [value length] > 5;
}] subscribeNext:^(id x) {
// 返回值就是过滤条件,只有满足这个条件才能获取到内容
NSLog(@"过滤后的值为:%@",x);
}];
}
输出结果为:
2020-09-22 16:47:58.559138+0800 框架Demo[83318:17131897] 过滤后的值为:123456
❷ ignore
作用
忽略某些值的信号。
Demo演示
- (void)ignoreDemo
{
// ignore:忽略一些值
// ignoreValues:忽略所有的值
// 1.创建信号
RACSubject *subject = [RACSubject subject];
// 2.忽略值
RACSignal *ignoreSignal = [subject ignore:@"456"];
// 3.订阅信号
[ignoreSignal subscribeNext:^(id x) {
NSLog(@"忽略后的值为:%@",x);
}];
// 4.发送数据
[subject sendNext:@"2"];
[subject sendNext:@"456"];
[subject sendNext:@"789"];
}
输出结果为:
2020-09-22 16:51:23.320604+0800 框架Demo[83405:17137309] 忽略后的值为:2
2020-09-22 16:51:23.320997+0800 框架Demo[83405:17137309] 忽略后的值为:789
❸ distinctUntilChanged
作用
当上一次的值和当前的值有明显的变化就会发出信号,否则会被忽略掉。
Demo演示
- (void)distinctUntilChangedDemo
{
// distinctUntilChanged:如果当前的值跟上一个值相同,就不会被订阅到
RACSubject *subject = [RACSubject subject];
[[subject distinctUntilChanged] subscribeNext:^(id x) {
NSLog(@"区别后的值为:%@",x);
}];
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@2];
[subject sendNext:@3];
}
输出结果为:
2020-09-22 16:51:23.320604+0800 框架Demo[83405:17137309] 忽略后的值为:2
2020-09-22 16:51:23.320997+0800 框架Demo[83405:17137309] 忽略后的值为:789
❹ take
作用
从开始一共取N次的信号。
Demo演示
- (void)takeDemo
{
// 1.创建信号
RACSubject *subject = [RACSubject subject];
// 2、处理信号,订阅信号
[[subject take:2] subscribeNext:^(id x) {
NSLog(@"信号值为:%@",x);
}];
// 3.发送信号
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@3];
}
输出结果为:
2020-09-22 17:34:03.475913+0800 框架Demo[83895:17167666] 信号值为:1
2020-09-22 17:34:03.476073+0800 框架Demo[83895:17167666] 信号值为:2
❺ takeLast
作用
取最后N次的信号,前提条件是订阅者必须调用完成,因为只有完成,才知道总共有多少信号。
Demo演示
- (void)takeLastDemo
{
// 1.创建信号
RACSubject *signal = [RACSubject subject];
// 2、处理信号,订阅信号
[[signal takeLast:1] subscribeNext:^(id x) {
NSLog(@"信号值为:%@",x);
}];
// 3.发送信号
[signal sendNext:@1];
[signal sendNext:@2];
[signal sendNext:@3];
[signal sendCompleted];
}
输出结果为:
2020-09-22 17:37:03.683675+0800 框架Demo[83955:17170666] 信号值为:3
❻ takeUntil
作用
获取信号直到执行完这个信号,当发送信号为空或结束时,后面有发送的信号,也不会执行。
Demo演示
- (void)takeUntilDemo
{
RACSubject *subject = [RACSubject subject];
RACSubject *signal = [RACSubject subject];
[[subject takeUntil:signal] subscribeNext:^(id x) {
NSLog(@"信号值为:%@",x);
}];
[subject sendNext:@1];
[subject sendNext:@"abc"];
[signal sendError:nil];
[signal sendNext:@2];
[signal sendNext:@3];
}
输出结果为:
2020-09-22 17:40:08.429672+0800 框架Demo[84011:17173856] 信号值为:1
2020-09-22 17:40:08.429799+0800 框架Demo[84011:17173856] 信号值为:abc
❼ skip
作用
跳过几个信号,不接受。
Demo演示
- (void)skipDemo
{
// skip;跳跃几个值
RACSubject *subject = [RACSubject subject];
[[subject skip:2] subscribeNext:^(id x) {
NSLog(@"跳过后的值为:%@",x);
}];
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@3];
}
输出结果为:
2020-09-22 17:42:35.597149+0800 框架Demo[84054:17176772] 跳过后的值为:3
11、操作方法之秩序
// doNext和doCompleted的使用
- (void)orderDemo
{
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@123];
[subscriber sendCompleted];
return nil;
}];
[[[signal doNext:^(id x) {
// 执行[subscriber sendNext:@123];之前会调用这个Block
NSLog(@"doNext");
}] doCompleted:^{
// 执行[subscriber sendCompleted];之前会调用这个Block
NSLog(@"doCompleted");
}] subscribeNext:^(id x) {
NSLog(@"信号值为:%@",x);
}];
}
输出结果为:
2020-09-22 17:47:26.881166+0800 框架Demo[84129:17181460] doNext
2020-09-22 17:47:26.881297+0800 框架Demo[84129:17181460] 信号值为:123
2020-09-22 17:47:26.881406+0800 框架Demo[84129:17181460] doCompleted
在不改变信号的基础上,进行一些附加的操作,比如说在订阅到给label
赋值前,改变label
的背景色。
运行效果如下:

12、操作方法之线程
RACScheduler
是RAC
中用GCD
封装的队列。因为信号的流转及操作都是在block
中完成的,也就是说大部分操作都是在子线程中执行的操作,但是有时候需要回到主线程完成一些事情,比如,请求到数据后,要刷新UI
,这就必须回到主线程[RACScheduler mainThreadScheduler]
,RAC
提供了这样的方法deliverOn
。
文本框
// deliverOn的使用
- (void)deliverOnDemo
{
@weakify(self)
[[[[[self autoDeleteSubscription] then:^RACSignal *{
@strongify(self);
return self.RACSchedulerTextField.rac_textSignal;
}] filter:^BOOL(NSString* value) {
return value.length > 3 ? YES : NO;
}] deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSString * value) {
@strongify(self);
// 回到主线程更新UI
self.RACSchedulerLabel.text = value;
NSLog(@"当前线程为:%@,value为:%@",[NSThread currentThread],value);
} error:^(NSError * _Nullable error) {
NSLog(@"出错了:%@",error);
}];
}
输出结果为:
2020-09-16 15:00:18.771928+0800 框架Demo[58376:9488070] 当前线程为:<NSThread: 0x6000028e8b80>{number = 1, name = main},value为:1234
2020-09-16 15:00:19.098310+0800 框架Demo[58376:9488070] 当前线程为:<NSThread: 0x6000028e8b80>{number = 1, name = main},value为:12345
这样的话,实现的效果就是,在textField
中输入文字而且当文字大于3的时候,会在label
中显示出来,而且可以看到订阅信号的block
中打印出来的线程是主线程。
网络请求和图片加载
运行效果

使用系统的方法加载图片
- (void)requestImage
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSURL *imageURL = [NSURL URLWithString:@"https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=2789775069,1607374561&fm=26&gp=0.jpg"];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageURL]];
if (image != nil)
{
dispatch_async(dispatch_get_main_queue(), ^{
self.RACSchedulerImageView.image = image;
});
}
});
}
使用RAC方法请求图片
创建一个加载图片的方法,方法返回的是RACSignle
信号对象:
- (RACSignal *)RACRequestImage
{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSURL *imageURL = [NSURL URLWithString:@"https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=2789775069,1607374561&fm=26&gp=0.jpg"];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageURL]];
[subscriber sendNext:image];
[subscriber sendCompleted];
return nil;
}];
}
使用RAC方法加载图片
self.RACSchedulerButton = [[UIButton alloc] initWithFrame:CGRectMake(120, 720, 200, 100)];
self.RACSchedulerButton.backgroundColor = [UIColor blackColor];
[self.RACSchedulerButton setTitle:@"请求图片" forState:UIControlStateNormal];
@weakify(self);
[[[[self.RACSchedulerButton rac_signalForControlEvents:(UIControlEventTouchUpInside)]
flattenMap:^RACSignal *(id value) {
return [self RACRequestImage];
}] deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(UIImage * image) {
@strongify(self);
self.RACSchedulerImageView.image = image;
}];
[self.view addSubview:self.RACSchedulerButton];
13、操作方法之时间
❶ timeout
作用
超时,可以让一个信号在一定的时间后,自动报错。
Demo演示
- (void)timeoutDemo
{
RACSignal *signalA = [[RACSignal createSignal:^RACDisposable *(id subscriber) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@123];
});
return nil;
}] timeout:1 onScheduler:[RACScheduler currentScheduler]];
[signalA subscribeNext:^(id x) {
// 模拟2秒后才会发送消息
NSLog(@"信号值为:%@",x);
} error:^(NSError *error) {
// 1秒后超时会自动调用
NSLog(@"错误为:%@",error);
}];
}
输出结果为:
2020-09-22 18:01:23.179490+0800 框架Demo[84311:17192607] 错误为:Error Domain=RACSignalErrorDomain Code=1 "(null)"
❷ interval
作用
每隔一段时间发出信号。
Demo演示
- (void)intervalDemo
{
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@123];
return nil;
}] delay:2.0];// delay延迟发送next
[signal subscribeNext:^(id x) {
NSLog(@"信号值为:%@",x);
}];
}
输出结果为:
2020-09-22 18:05:17.417663+0800 框架Demo[84383:17196230] 信号值为:123
14、操作方法之重复
❶ retry
作用
只要失败,就会重新执行创建信号中的block,直到成功。
Demo演示
- (void)retryDemo
{
__block int i = 0;
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
if (i == 5)
{
[subscriber sendNext:@"谢佳培"];
}
else
{
NSLog(@"这名字不好听");
[subscriber sendError:nil];
}
I++;
return nil;
}];
[[signal retry] subscribeNext:^(id x) {
NSLog(@"信号值为:%@",x);
} error:^(NSError *error) {
NSLog(@"错误信息为:%@",error);
}];
}
输出结果为:
2020-09-22 18:09:34.601954+0800 框架Demo[84444:17200030] 这名字不好听
2020-09-22 18:09:34.605102+0800 框架Demo[84444:17200030] 这名字不好听
2020-09-22 18:09:34.605266+0800 框架Demo[84444:17200030] 这名字不好听
2020-09-22 18:09:34.605386+0800 框架Demo[84444:17200030] 这名字不好听
2020-09-22 18:09:34.605497+0800 框架Demo[84444:17200030] 这名字不好听
2020-09-22 18:09:34.605635+0800 框架Demo[84444:17200030] 信号值为:谢佳培
❷ replay
作用
当一个信号被多次订阅,反复播放内容。
Demo演示
- (void)replayDemo
{
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@123];
[subscriber sendNext:@456];
return nil;
}];
[[signal replay] subscribeNext:^(id x) {
NSLog(@"信号值为:%@",x);
}];
[[signal replay] subscribeNext:^(id x) {
NSLog(@"信号值为:%@",x);
}];
}
输出结果为:
2020-09-22 18:15:07.998935+0800 框架Demo[84523:17205138] 信号值为:123
2020-09-22 18:15:07.999077+0800 框架Demo[84523:17205138] 信号值为:456
2020-09-22 18:15:07.999240+0800 框架Demo[84523:17205138] 信号值为:123
2020-09-22 18:15:07.999328+0800 框架Demo[84523:17205138] 信号值为:456
❸ throttle
作用
当某个信号发送比较频繁时,可以使用节流,如果前面信号在设定的时间内没有变化时,throttle
就会把信号传到下面的事件中去。用文本框textField
作比喻,当我在里面输入字符时,subscribeNext
的block
会不停的走,每输入一个字符,就会走一遍。如果我想在输入过程中不需要每改变一个字符就走一遍,而是等输入完成或停止的时候再走block
里面的代码,那就可以用throttle
。
Demo演示
- (void)throttleDemo
{
RACSubject *signal = [RACSubject subject];
_signal = signal;
// 节流,在一定时间(1秒)内,不接收任何信号内容,过了这个时间(1秒)获取最后发送的信号内容发出
[[signal throttle:1.0 valuesPassingTest:^BOOL(id next) {
return YES;
}] subscribeNext:^(id x) {
NSLog(@"信号值为:%@",x);
}];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[_signal sendNext:@(i)];
I++;
}
输出结果为:
2020-09-22 18:21:08.508250+0800 框架Demo[84618:17210393] 信号值为:0
2020-09-22 18:21:10.762703+0800 框架Demo[84618:17210393] 信号值为:1
2020-09-22 18:21:13.057885+0800 框架Demo[84618:17210393] 信号值为:2
2020-09-22 18:21:15.037305+0800 框架Demo[84618:17210393] 信号值为:3
2020-09-22 18:21:16.995578+0800 框架Demo[84618:17210393] 信号值为:4
15、RAC双向绑定
何谓双向绑定?例如两个UITextFidle
,任意一个UITextFidle
输入文本变化时,另一个也要跟着变化。代码很简单,RAC
给UITextFidle
添加了分类方法- (RACChannelTerminal *)rac_newTextChannel;
,可以很简单的去生成RACChannelTerminal
,来实现双向绑定。同样也给UITextView
,UISwitch
,UIStepper
,UISlider
,UISegmentedControl
,UIControl
,UIDatePicker
控件添加了相应的分类方法去生成RACChannelTerminal
。
上面例子中的代码也很简单:
- (void)channelDemo
{
[self.channelFirstTextField.rac_newTextChannel subscribe:self.channelSecondTextField.rac_newTextChannel];
[self.channelSecondTextField.rac_newTextChannel subscribe:self.channelFirstTextField.rac_newTextChannel];
}
运行效果如下:

❶ 什么是RACChannel和RACChannelTerminal呢?
RACChannel
可以看成是一个双向通道,由两个并行工作的可控信号组成。而RACChannelTerminal
则是这个双向通道的一端。可以简单理解为,两个属性双向监听,相当于在这两个属性直接建立个一个通道,而其中的一端就是RACChannelTerminal
。类比于网络编程里面socket
的概念,RACChannel
类似网络链接通道,RACChannelTerminal
类似于socket
。
❷ 直接使用rac_newTextChannel实现UITextField的双向绑定是有隐患的
- (void)rac_newTextChannelBug
{
// 使用RACChannelTo双向绑定
RACChannelTo(self, channelString) = self.channelFirstTextField.rac_newTextChannel;
// 监控键盘输入时候两个值的变化
[self.channelFirstTextField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"channelString的值为:%@",self.channelString);
NSLog(@"channelFirstTextField的文本值为:%@",x);
}];
}
- (void)viewDidLoad {
[super viewDidLoad];
[self createChannelSubviews];
[self rac_newTextChannelBug];
self.channelString = @"888888";// string改变
// self.channelFirstTextField.text = @"888888";// textField.text 改变
NSLog(@"channelString的值为:%@",self.channelString);
NSLog(@"channelFirstTextField的文本值为:%@",self.channelFirstTextField.text);
}
㊀ 通过键盘给textField.text
赋值的情况。
均注释
// self.channelString = @"888888";
// self.channelFirstTextField.text = @"888888";
输出结果显示textField.text
和string
均改变:
2020-09-23 11:33:17.312011+0800 框架Demo[86497:17335341] channelString的值为:1
2020-09-23 11:33:17.312148+0800 框架Demo[86497:17335341] channelFirstTextField的文本值为:1
2020-09-23 11:33:17.662046+0800 框架Demo[86497:17335341] channelString的值为:12
2020-09-23 11:33:17.662146+0800 框架Demo[86497:17335341] channelFirstTextField的文本值为:12
2020-09-23 11:33:18.011832+0800 框架Demo[86497:17335341] channelString的值为:123
2020-09-23 11:33:18.011933+0800 框架Demo[86497:17335341] channelFirstTextField的文本值为:123
2020-09-23 11:33:19.833403+0800 框架Demo[86497:17335341] channelString的值为:1234
2020-09-23 11:33:19.833493+0800 框架Demo[86497:17335341] channelFirstTextField的文本值为:1234
㊁ 通过代码赋值,打开改变string
的注释。输出结果显示textField.text
和string
均改变。
2020-09-23 11:37:31.968805+0800 框架Demo[86561:17339061] channelString的值为:(null)
2020-09-23 11:37:31.968918+0800 框架Demo[86561:17339061] channelFirstTextField的文本值为:
2020-09-23 11:37:31.971041+0800 框架Demo[86561:17339061] channelString的值为:888888
2020-09-23 11:37:31.971151+0800 框架Demo[86561:17339061] channelFirstTextField的文本值为:888888
㊂ 通过代码赋值,打开改变textField.text
的注释。输出结果显示string
不改变,textField.text
改变。
2020-09-23 11:39:28.120200+0800 框架Demo[86601:17341391] channelString的值为:(null)
2020-09-23 11:39:28.120353+0800 框架Demo[86601:17341391] channelFirstTextField的文本值为:
2020-09-23 11:39:28.122432+0800 框架Demo[86601:17341391] channelString的值为:(null)
2020-09-23 11:39:28.122565+0800 框架Demo[86601:17341391] channelFirstTextField的文本值为:888888
❸ 直接使用RACChannelTo实现UITextField的双向绑定是有隐患的
- (void)RACChannelToBug
{
// 使用RACChannelTo双向绑定
RACChannelTo(self, channelString) = RACChannelTo(self.channelSecondTextField, text);
// 监控键盘输入时候两个值的变化
[self.channelSecondTextField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"channelString的值为:%@",self.channelString);
NSLog(@"channelSecondTextField的文本值为:%@",x);
}];
}
㊀ 通过键盘给textField.text
赋值的情况。均注释,输出结果显示textField.text
改变,而string
不改变。
2020-09-23 13:40:50.986948+0800 框架Demo[87869:17401966] channelFirstTextField的文本值为:1
2020-09-23 13:40:51.274946+0800 框架Demo[87869:17401966] channelString的值为:
2020-09-23 13:40:51.275046+0800 框架Demo[87869:17401966] channelFirstTextField的文本值为:12
2020-09-23 13:40:51.679521+0800 框架Demo[87869:17401966] channelString的值为:
2020-09-23 13:40:51.679617+0800 框架Demo[87869:17401966] channelFirstTextField的文本值为:123
2020-09-23 13:40:53.389484+0800 框架Demo[87869:17401966] channelString的值为:
㊁ 通过代码赋值,打开改变string
的注释。输出结果显示textField.text
和string
均改变。
2020-09-23 13:42:44.182341+0800 框架Demo[87901:17403933] channelString的值为:
2020-09-23 13:42:44.182438+0800 框架Demo[87901:17403933] channelFirstTextField的文本值为:
2020-09-23 13:42:44.184201+0800 框架Demo[87901:17403933] channelString的值为:888888
2020-09-23 13:42:44.184306+0800 框架Demo[87901:17403933] channelFirstTextField的文本值为:888888
㊂ 通过代码赋值,打开改变textField.text
的注释。输出结果显示textField.text
和string
均改变。
2020-09-23 13:43:16.926716+0800 框架Demo[87913:17404857] channelString的值为:
2020-09-23 13:43:16.926805+0800 框架Demo[87913:17404857] channelFirstTextField的文本值为:
2020-09-23 13:43:16.928547+0800 框架Demo[87913:17404857] channelString的值为:888888
2020-09-23 13:43:16.928644+0800 框架Demo[87913:17404857] channelFirstTextField的文本值为:888888
❹ 问题总结
无论是使用RACChannelTo
还是rac_newTextChannel
,string
的值改变的时候,textField.text
都会跟着改变,也就是说string -> textField.text
的这个单向通道是没问题的。
问题就在于无论是通过键盘或者代码赋值来改变textField.text
的值的时候,我们都要让绑定的string
对应的改变,这才是我们要的双向绑定。
rac_newTextChannel:当通过键盘改变textField. text
的值的时候才会把这个值发送出去。
RACChannelTo:当通过代码改变textField.text
的值的时候才会把这个值发送出去。
❺ 正确解决方案
- (void)rightChannelDemo
{
RACChannelTo(self, channelString) = RACChannelTo(self.channelFirstTextField, text);
@weakify(self);
[self.channelFirstTextField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
@strongify(self);
self.channelString = x;
NSLog(@"channelString的值为:%@",self.channelString);
NSLog(@"channelFirstTextField的文本值为:%@",x);
}];
}
简化代码:
- (void)rightChannelSimpleDemo
{
RACChannelTo(self, channelString) = RACChannelTo(self.channelFirstTextField, text);
[self.channelFirstTextField.rac_textSignal subscribe:RACChannelTo(self, channelString)];
}
㊀ 通过键盘给textField.text
赋值的情况。输出结果显示textField.text
和string
均改变。
2020-09-23 13:49:32.150812+0800 框架Demo[87998:17410375] channelString的值为:1
2020-09-23 13:49:32.150917+0800 框架Demo[87998:17410375] channelFirstTextField的文本值为:1
2020-09-23 13:49:32.388977+0800 框架Demo[87998:17410375] channelString的值为:12
2020-09-23 13:49:32.389083+0800 框架Demo[87998:17410375] channelFirstTextField的文本值为:12
2020-09-23 13:49:32.688164+0800 框架Demo[87998:17410375] channelString的值为:123
2020-09-23 13:49:32.688271+0800 框架Demo[87998:17410375] channelFirstTextField的文本值为:123
㊁ 通过代码赋值,打开改变string
的注释。输出结果显示textField.text
和string
均改变。
2020-09-23 13:50:43.195027+0800 框架Demo[88028:17412036] channelString的值为:
2020-09-23 13:50:43.195181+0800 框架Demo[88028:17412036] channelFirstTextField的文本值为:
2020-09-23 13:50:43.197267+0800 框架Demo[88028:17412036] channelString的值为:888888
2020-09-23 13:50:43.197393+0800 框架Demo[88028:17412036] channelFirstTextField的文本值为:888888
㊂ 通过代码赋值,打开改变textField.text
的注释。输出结果显示textField.text
和string
均改变。
2020-09-23 13:51:17.817595+0800 框架Demo[88045:17414054] channelString的值为:
2020-09-23 13:51:17.817716+0800 框架Demo[88045:17414054] channelFirstTextField的文本值为:
2020-09-23 13:51:17.819569+0800 框架Demo[88045:17414054] channelString的值为:888888
2020-09-23 13:51:17.819679+0800 框架Demo[88045:17414054] channelFirstTextField的文本值为:888888
满足上面三点就算完全的双向绑定。UITextView
的情况及处理跟UITextField
一样。
❻ 错误解决方案:使用rac_textSignal
- (void)wrongChannelDemo
{
RACChannelTo(self, channelString) = self.channelFirstTextField.rac_newTextChannel;
// 当textField.text改变的时候,会回调这个block,然后再给string赋值,实现双向绑定
@weakify(self);
[self.channelFirstTextField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
@strongify(self);
self.channelString = x;
NSLog(@"channelString的值为:%@",self.channelString);
NSLog(@"channelFirstTextField的文本值为:%@",x);
}];
}
通过代码赋值,打开改变textField.text
的注释。输出结果显示textField.text
改变,而string
不改变。
2020-09-23 13:58:12.239358+0800 框架Demo[88234:17423565] channelString的值为:
2020-09-23 13:58:12.239473+0800 框架Demo[88234:17423565] channelFirstTextField的文本值为:
2020-09-23 13:58:12.241037+0800 框架Demo[88234:17423565] channelString的值为:
2020-09-23 13:58:12.241145+0800 框架Demo[88234:17423565] channelFirstTextField的文本值为:888888
代码赋值的时候,textField.rac_textSignal
不会触发回调,原因 是rac_textSignal
这个signal
是监听UIControlEventAllEditingEvents
的,所以对setter
方式不会触发signal
。
进入rac_textSignal
的源码实现可以看到:
concat:[self rac_signalForControlEvents:UIControlEventAllEditingEvents]]
concat:
连接信号,第一个信号必须发送完成,第二个信号才会被激活。也就是说,必须触发UIControlEventAllEditingEvents
才会触发rac_textSignal
,所以用setter
的方式赋值就不行了。
❼ 仍然存在的问题及解决方案
RACChannelTo(model, string) = textField.rac_newTextChannel; // ui 到 model 的绑定
RACChannelTo(textField, text) = RACChannelTo(model, string); // model 到 ui 的绑定
但是用RACChannelTo
的方式实现双向绑定有一个痛点:如果要绑定model
中的基本数据类型(age, price,count....)
,该如何操作?用rac
提供的map
方法会返回一个RACSignal
类型,并不能再次转化为RACChannelTerminal
类型。
换一种做法是不使用RACChannel
😂,之前的白说了吗?。而是直接用RAC()
和RACObeserve()
宏来实现。
// model - > ui
RAC(name_tf, text) = RACObserve(_person, name);
RAC(age_tf, text) = [RACObserve(_person, age) map:^id _Nullable(id _Nullable value) {
return [value description];
}];
// ui -> model
RAC(_person, name) = name_tf.rac_textSignal;
RAC(_person, age) = age_tf.rac_textSignal;
二、ReactiveCocoa的源码解析
本着饮水思源的想法,面对ReactiveCocoa
的强大功能,按捺不住心中的好奇心,于是走进其源码之中,一探ReactiveCocoa的魅力所在。虽然,耳闻其强大功能的核心是:信号,但一直不知道这个信号是如何产生、如何传递,又是如何被处理的。曾经以为信号传递是通知,但是真正读了源码后,才发现之前的想法有多不妥,而人家的实现又是多巧妙。
1、RACSignal
a、创建信号
RACSignal.m
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
return [RACDynamicSignal createSignal:didSubscribe];
}
RACDynamicSignal.m
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
// 创建了一个RACDynamicSignal类的信号
RACDynamicSignal *signal = [[self alloc] init];
// 将代码块保存到信号里面(但此时仅仅是保存,没有调用),所以信号还是冷信号
signal->_didSubscribe = [didSubscribe copy];
return [signal setNameWithFormat:@"+createSignal:"];
}
b、订阅信号
RACSignal.m
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock completed:(void (^)(void))completedBlock {
NSCParameterAssert(nextBlock != NULL);
NSCParameterAssert(completedBlock != NULL);
// 内部创建了RACSubscriber(订阅者)类的实例对象o,并且将nextBlock保存到o中
// 在返回值处执行o,实际也是执行了nextBlock
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:completedBlock];
return [self subscribe:o];
}
RACSubscriber.m
+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {
RACSubscriber *subscriber = [[self alloc] init];
// 将block保存到subscriber中
subscriber->_next = [next copy];
subscriber->_error = [error copy];
subscriber->_completed = [completed copy];
return subscriber;
}
RACDynamicSignal.m
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCParameterAssert(subscriber != nil);
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
// 判断有无self.didSubscribe
if (self.didSubscribe != NULL) {
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
// 有则执行该self.didSubscribe,意味着将订阅者subscriber发送过去
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
[disposable addDisposable:innerDisposable];
}];
[disposable addDisposable:schedulingDisposable];
}
return disposable;
}
RACPassthroughSubscriber.m
- (instancetype)initWithSubscriber:(id<RACSubscriber>)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable {
NSCParameterAssert(subscriber != nil);
self = [super init];
// 保存订阅者,信号,处理操作
_innerSubscriber = subscriber;
_signal = signal;
_disposable = disposable;
[self.innerSubscriber didSubscribeWithDisposable:self.disposable];
return self;
}
c、发送信号
RACSubscriber.m
- (void)sendNext:(id)value {
@synchronized (self) {
// 名为next的block是返回值为void,参数为id类型的value
// 在sendNext:内部,将next复制给nextBlock
void (^nextBlock)(id) = [self.next copy];
if (nextBlock == nil) return;
// 执行该方法后,subscribeNext:的block参数才会被调用
nextBlock(value);
}
}
d、流程总结
- 创建信号:首先把
didSubscribe
保存到信号中,还不会触发。 - 订阅信号:当信号被订阅,也就是调用
signal
的subscribeNext:nextBlock
。
2.1subscribeNext
内部会创建订阅者subscriber
,并且把nextBlock
保存到subscriber
中。
2.2subscribeNext
内部会调用siganl
的didSubscribe
。 - 发送数据:
siganl
的didSubscribe
中调用[subscriber sendNext:@1];
。
3.1sendNext
底层其实就是执行subscriber
的nextBlock
。
2、RACSubject
RACSubscriber
表示订阅者的意思,用于发送信号,这是一个协议,不是一个类,只要遵守这个协议,并且实现方法才能成为订阅者。通过create
创建的信号,都有一个订阅者,帮助他发送数据。
RACDisposable
用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它。
a、创建信号
RACSubject.m
+ (instancetype)subject {
return [[self alloc] init];
}
- (instancetype)init {
self = [super init];
if (self == nil) return nil;
_disposable = [RACCompoundDisposable compoundDisposable];
_subscribers = [[NSMutableArray alloc] initWithCapacity:1];
return self;
}
b、订阅信号
RACSubject
订阅信号的实质就是将内部创建的订阅者保存在订阅者数组self.subscribers
中,仅此而已。订阅者对象有一个名为nextBlock
的block
参数。
RACSignal.m
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
return [self subscribe:o];
}
RACSubscriber.m
+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {
RACSubscriber *subscriber = [[self alloc] init];
// 将block保存到subscriber中
subscriber->_next = [next copy];
subscriber->_error = [error copy];
subscriber->_completed = [completed copy];
return subscriber;
}
RACSubject.m
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCParameterAssert(subscriber != nil);
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
NSMutableArray *subscribers = self.subscribers;
@synchronized (subscribers) {
[subscribers addObject:subscriber];
}
[disposable addDisposable:[RACDisposable disposableWithBlock:^{
@synchronized (subscribers) {
// Since newer subscribers are generally shorter-lived, search
// starting from the end of the list.
NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id<RACSubscriber> obj, NSUInteger index, BOOL *stop) {
return obj == subscriber;
}];
if (index != NSNotFound) [subscribers removeObjectAtIndex:index];
}
}]];
return disposable;
}
c、发送信号
- (void)sendNext:(id)value {
[self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
[subscriber sendNext:value];
}];
}
- (void)enumerateSubscribersUsingBlock:(void (^)(id<RACSubscriber> subscriber))block {
NSArray *subscribers;
@synchronized (self.subscribers) {
subscribers = [self.subscribers copy];
}
// 1. 先遍历订阅者数组中的订阅者
for (id<RACSubscriber> subscriber in subscribers) {
// 2. 后执行订阅者中的nextBlock
// 3. 最后让订阅者发送信号
block(subscriber);
}
}
d、流程总结
流程
- 创建信号。
1.1 同时创建subscribers
数组来保存订阅者。 - 调用
subscribeNext
订阅信号。
2.1 内部创建subscriber
订阅者并保存到subscribers
。
2.2 订阅者对象中有个nextBlock
并且已经赋值了。 - 发送数据。订阅者对象调用
sendNext
发送信号。 - 遍历刚刚保存的所有订阅者,一个接一个调用订阅者的
nextBlock
。
注意
- 由于本质是将订阅者保存到数组中,所以可以有多个订阅者订阅信息。
- 必须先订阅,后发送信息。订阅信号就是创建订阅者的过程,如果不先订阅,数组中就没有订阅者对象,那就无法通过订阅者发送消息。
3、RACReplaySubject
RACReplaySubject
是RACSubject
的子类。由于每次发送信号时,会先保存nextBlock
,然后调用父类的sendNext
方法,遍历订阅者,执行信号;而每次订阅信号时,会从valuesReceived
中取值,然后调用sendNext
方法,遍历订阅者,执行信号。所以,订阅和发送没有先后顺序。
a、创建信号
RACSubject.m
+ (instancetype)subject {
return [[self alloc] init];
}
RACReplaySubject.m
+ (instancetype)replaySubjectWithCapacity:(NSUInteger)capacity {
return [(RACReplaySubject *)[self alloc] initWithCapacity:capacity];
}
- (instancetype)init {
return [self initWithCapacity:RACReplaySubjectUnlimitedCapacity];
}
- (instancetype)initWithCapacity:(NSUInteger)capacity {
self = [super init];
_capacity = capacity;
// 会用这个数组保存值value
_valuesReceived = (capacity == RACReplaySubjectUnlimitedCapacity ? [NSMutableArray array] : [NSMutableArray arrayWithCapacity:capacity]);
return self;
}
b、订阅信号
RACSignal.m
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
return [self subscribe:o];
}
RACReplaySubject.m
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
@synchronized (self) {
for (id value in self.valuesReceived) {
if (compoundDisposable.disposed) return;
[subscriber sendNext:(value == RACTupleNil.tupleNil ? nil : value)];
}
if (compoundDisposable.disposed) return;
if (self.hasCompleted) {
[subscriber sendCompleted];
} else if (self.hasError) {
[subscriber sendError:self.error];
} else {
RACDisposable *subscriptionDisposable = [super subscribe:subscriber];
[compoundDisposable addDisposable:subscriptionDisposable];
}
}
}];
[compoundDisposable addDisposable:schedulingDisposable];
return compoundDisposable;
}
c、发送信号
- (void)sendNext:(id)value {
@synchronized (self) {
// 发送信号的时候,会先将值value保存到数组中
[self.valuesReceived addObject:value ?: RACTupleNil.tupleNil];
if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) {
[self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)];
}
// 调用父类发送(先遍历订阅者,然后发送值value)
[super sendNext:value];
}
}
d、流程总结
- 创建信号。
1.1 同时创建subscribers
数组来保存订阅者。 - 调用
subscribeNext
订阅信号。
2.1 内部创建subscriber
订阅者并保存到subscribers
。
2.2 订阅者对象中有个nextBlock
并且已经赋值了。 - 遍历
self.valuesReceived
,然后给subscribers
发送。 - 订阅者对象调用
sendNext
发送信号。 - 保存订阅者的
nextBlock
到self.valuesReceived
。 - 调用父类的
sendNext
遍历订阅者,执行nextBlock
。
4、RACMulticastConnection
RACMulticastConnection
用于当一个信号被多次订阅时,为了保证创建信号时,避免多次调用创建信号的block
造成副作用,可以使用该类处理,保证创建信号的block
执行一次。
a、创建信号
RACDynamicSignal.m
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
// 创建了一个RACDynamicSignal类的信号
RACDynamicSignal *signal = [[self alloc] init];
// 将代码块保存到信号里面(但此时仅仅是保存,没有调用),所以信号还是冷信号
signal->_didSubscribe = [didSubscribe copy];
return [signal setNameWithFormat:@"+createSignal:"];
}
b、创建连接
RACSignal+Operations.m
- (RACMulticastConnection *)publish {
// 创建订阅者
RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name];
// 创建connection,参数是刚才创建的订阅者
RACMulticastConnection *connection = [self multicast:subject];
return connection;
}
- (RACMulticastConnection *)multicast:(RACSubject *)subject {
[subject setNameWithFormat:@"[%@] -multicast: %@", self.name, subject.name];
RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject];
return connection;
}
RACMulticastConnection.m
- (instancetype)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject {
NSCParameterAssert(source != nil);
NSCParameterAssert(subject != nil);
self = [super init];
// 保存原始信号
_sourceSignal = source;
_serialDisposable = [[RACSerialDisposable alloc] init];
// 保存订阅者,即_signal是RACSubject对象
_signal = subject;
return self;
}
c、订阅信号
RACSignal.m
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
return [self subscribe:o];
}
RACSubscriber.m
+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {
RACSubscriber *subscriber = [[self alloc] init];
// 将block保存到subscriber中
subscriber->_next = [next copy];
subscriber->_error = [error copy];
subscriber->_completed = [completed copy];
return subscriber;
}
RACSubject.m
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCParameterAssert(subscriber != nil);
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
NSMutableArray *subscribers = self.subscribers;
@synchronized (subscribers) {
[subscribers addObject:subscriber];
}
[disposable addDisposable:[RACDisposable disposableWithBlock:^{
@synchronized (subscribers) {
// Since newer subscribers are generally shorter-lived, search
// starting from the end of the list.
NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id<RACSubscriber> obj, NSUInteger index, BOOL *stop) {
return obj == subscriber;
}];
if (index != NSNotFound) [subscribers removeObjectAtIndex:index];
}
}]];
return disposable;
}
d、连接信号
RACMulticastConnection.m
- (RACDisposable *)connect {
BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);
if (shouldConnect) {
// 订阅原生信号
self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
}
return self.serialDisposable;
}
RACDynamicSignal.m
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCParameterAssert(subscriber != nil);
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
// 判断有无self.didSubscribe
if (self.didSubscribe != NULL) {
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
// 有则执行该self.didSubscribe,意味着将订阅者subscriber发送过去
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
[disposable addDisposable:innerDisposable];
}];
[disposable addDisposable:schedulingDisposable];
}
return disposable;
}
RACSubject.m
- (void)sendNext:(id)value {
// 遍历_subscribers数组,执行nextBlock
[self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
[subscriber sendNext:value];
}];
}
e、流程总结
- 创建
connect
。
1.1connect.sourceSignal
->RACSignal
(原始信号)
1.2connect.signal
->RACSubject
- 订阅
connect.signal
。
2.1 会调用RACSubject
的subscribeNext
,创建订阅者,并且把订阅者保存起来,不会执行block
。 -
[connect connect]
内部会订阅RACSignal
(原始信号),并且订阅者是RACSubject
。
3.1 订阅原始信号,就会调用原始信号中的didSubscribe
3.2 通过didSubscribe
拿到订阅者再调用sendNext
,其实是调用RACSubject
的sendNext
。 -
RACSubject
的sendNext
会遍历RACSubject
所有订阅者并发送信号。
4.1 因为刚刚第二步,都是在订阅RACSubject
,因此会拿到第二步所有的订阅者,调用他们的nextBlock
。
5、RACCommand
RACCommand
类用来表示动作的执行,是对动作触发后的连锁事件的封装。常用在封装网络请求,按钮点击等等场合。RACCommand
用来封装事件时,还可以订阅信号(executionSignals
)、订阅最新信号(switchToLatest
)、跳过几次信号(skip
)、判断信号是否正在执行(executing
),在执行信号时,还可以监听错误信号和完成信号。
a、创建command
RACCommand.m
- (instancetype)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal<id> * (^)(id input))signalBlock {
NSCParameterAssert(signalBlock != nil);
self = [super init];
_addedExecutionSignalsSubject = [RACSubject new];
_allowsConcurrentExecutionSubject = [RACSubject new];
// 保存创建信号的block
_signalBlock = [signalBlock copy];
}
b、执行command
RACCommand.m
- (RACSignal *)execute:(id)input {
BOOL enabled = [[self.immediateEnabled first] boolValue];
if (!enabled) {
NSError *error = [NSError errorWithDomain:RACCommandErrorDomain code:RACCommandErrorNotEnabled userInfo:@{
NSLocalizedDescriptionKey: NSLocalizedString(@"The command is disabled and cannot be executed", nil),
RACUnderlyingCommandErrorKey: self
}];
return [RACSignal error:error];
}
RACSignal *signal = self.signalBlock(input);
NSCAssert(signal != nil, @"nil signal returned from signal block for value: %@", input);
// 创建连接,用RACReplaySubject作为订阅者
RACMulticastConnection *connection = [[signal
subscribeOn:RACScheduler.mainThreadScheduler]
multicast:[RACReplaySubject subject]];
[self.addedExecutionSignalsSubject sendNext:connection.signal];
// 连接信号
[connection connect];
return [connection.signal setNameWithFormat:@"%@ -execute: %@", self, RACDescription(input)];
}
RACMulticastConnection.m
- (RACDisposable *)connect {
BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);
if (shouldConnect) {
// 订阅原生信号
// 执行创建信号的block
self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
}
return self.serialDisposable;
}
c、订阅command
RACSignal.m
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
return [self subscribe:o];
}
RACReplaySubject.m
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
@synchronized (self) {
for (id value in self.valuesReceived) {
if (compoundDisposable.disposed) return;
[subscriber sendNext:(value == RACTupleNil.tupleNil ? nil : value)];
}
if (compoundDisposable.disposed) return;
if (self.hasCompleted) {
[subscriber sendCompleted];
} else if (self.hasError) {
[subscriber sendError:self.error];
} else {
RACDisposable *subscriptionDisposable = [super subscribe:subscriber];
[compoundDisposable addDisposable:subscriptionDisposable];
}
}
}];
[compoundDisposable addDisposable:schedulingDisposable];
return compoundDisposable;
}
Demo
Demo在我的Github上,欢迎下载。
SourceCodeAnalysisDemo
参考文献
iOS ReactiveCocoa框架的简单使用
RAC双向绑定UITextField的正确姿势
RAC中双向监听回声问题
网友评论