关于 RAC, 一直想写一篇文章来总结和整理一下, 可是一直没什么时间. 年末, 项目没那么忙, 就抽空整理一下 RAC 的用法. 本文参考了两篇文章, 并添加整合了一些自己的东西进去, 文章若有错误, 欢迎指正.
参考文章: ReactiveCocoa 进阶 , 最快让你上手 ReactiveCocoa 之基础篇
一, RAC 介绍
RAC 是一个 iOS 中的函数式响应式编程框架, 一般与 MVVM 配套使用.
在非 RAC 开发中, 都是习惯赋值. 在 RAC 开发中, 需要改变开发思维, 由赋值转变为绑定, 并不需要重写 set 方法.
RAC 项目的 Podfile 如下:
use_frameworks!
target '工程名称' do
pod 'ReactiveObjC', '~> 3.0.0'
end
如果使用 Swift 作为开发语言, 建议使用 RXSwift. 关于 RXSwift, 以后会单独写一篇文章介绍. RAC 使用注意事项:
1.RAC 学习曲线陡峭, 团队开发时要谨慎使用, 确保项目组所有成员都会 RAC, 再使用 RAC 构建项目. 如果一个人开发一个项目, 要使用 RAC, 先跟项目经理商量一下, 不要自己想当然的想用就用, 这样如果公司要加人, 会增加招人成本, 公司不一定乐意.
2. 在使用过程中, 尽量保持同一个项目的成员, 代码风格一致, 这样方便管理维护.
3. 关于学习曲线陡峭, 有的同学可能不以为然, 随便写个 demo, 体验了几个 RAC 基本操作方法, 就觉得自己已经会用 RAC 了. 其实不然, 要真正的用 RAC 构造整个项目, 再配上 MVVM, 是需要一个较长学习周期的.
二, RAC 常见用法
代替代理
rac_signalForSelector: 用于替代代理
// 需求: 自定义 redView, 监听红色 view 中按钮点击
// 之前都是需要通过代理监听, 给红色 View 添加一个代理属性, 点击按钮的时候, 通知代理做事情
// rac_signalForSelector: 把调用某个对象的方法的信息转换成信号, 就要调用这个方法, 就会发送信号.
// 这里表示只要 redV 调用 btnClick:, 就会发出信号, 订阅就好了.
[[redV rac_signalForSelector:@selector(btnClick:)] subscribeNext:^(id x) {
NSLog(@"点击红色按钮");
}];
代替 KVO
rac_valuesAndChangesForKeyPath: 用于监听某个对象的属性改变.
// 把监听 redV 的 center 属性改变转换成信号, 只要值改变就会发送信号
// observer: 可以传入 nil
[[redV rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
监听事件 (代替 addTarget)
rac_signalForControlEvents: 用于监听某个事件.
[[self.btn rac_signalForControlEvents:(UIControlEventTouchUpInside)]subscribeNext:^(__kindof UIControl * _Nullable x) {
NSLog(@"%@",x);
}];
代替通知
rac_addObserverForName: 用于监听某个通知
// 把监听到的通知转换信号
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
NSLog(@"键盘弹出");
}];
监听文本框文字改变
rac_textSignal: 只要文本框发出改变就会发出这个信号
[_textField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"文字改变了 %@",x);
}];
三, 常见类解释
RactiveCocoa 中很重要的两个 class, 一个是 RACSignal, 一个是 RACSequence, 而这两个 class 的 super class 就是 RACStream.
RACStream: 表示一个基本单元可以为任意值, 其值会随着事件的变化而变化. 可以在其上进行一些复杂的操作运算 (map,filter,skip,take 等.) 此类不会被经常使用, 多情况下表现为 signal 和 sequences(RACSignal 和 RACSequence 继承于 RACStream 类).
RACSiganl: 只是表示当数据改变时, 信号内部会发出数据, 它本身不具备发送信号的能力, 而是交给内部一个订阅者去发出. 默认一个信号都是冷信号, 也就是值改变了, 也不会触发, 只有订阅了这个信号, 这个信号才会变为热信号, 值改变了才会触发. signal 能发送 3 种不同类型的事件: Next,Completed,Error.
信号三部曲: 创建信号, 订阅信号, 发送信号.
// 1. 创建信号
RACSignal *siganl = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 2. 发送信号, 必须是一个对象类型
[subscriber sendNext:@1];
// 如果不在发送数据, 最好发送信号完成, 内部会自动调用 [RACDisposable disposable] 取消订阅信号.
[subscriber sendCompleted];
//return nil;
return [RACDisposable disposableWithBlock:^{
// block 调用时刻: 当信号发送完成或者发送错误, 就会自动执行这个 block, 取消订阅信号.
// 执行完 Block 后, 当前信号就不在被订阅了.
NSLog(@"信号被销毁");
}];
}];
// 3. 订阅信号, 才会激活信号.
[siganl subscribeNext:^(id x) {
// block 调用时刻: 每当有信号发出数据, 就会调用 block.
NSLog(@"接收到数据:%@",x);
}];
RACSubject: 信号提供者, 自己可以充当信号, 又能发送信号.
// 1. 创建信号
RACSubject *subject = [RACSubject subject];
// 2. 订阅信号
[subject subscribeNext:^(id x) {
// block 调用时刻: 当信号发出新值, 就会调用.
NSLog(@"第一个订阅者 %@",x);
}];
[subject subscribeNext:^(id x) {
// block 调用时刻: 当信号发出新值, 就会调用.
NSLog(@"第二个订阅者 %@",x);
}];
// 3. 发送信号
[subject sendNext:@"1"];
RACReplaySubject: 重复提供信号类, RACSubject 的子类.
RACDisposable: 用于取消订阅或者清理资源, 在一个 completed 或者 error 事件之后, 就会自动触发它, 订阅会自动移除. 也可以通过 RACDisposable 手动移除订阅.
RACTuple: 元组类, 类似 NSArray, 用来包装值.
RACSequence:RAC 中的集合类, 用于代替 NSArray,NSDictionary, 可以使用它来快速遍历数组和字典, 达到对值的一些过滤和转换.
// 遍历字典, 遍历出来的键值对会包装成 RACTuple(元组对象)
NSDictionary *dict = @{@"name":@"xmg",@"age":@18};
[dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
// 解包元组, 会把元组的值, 按顺序给参数里面的变量赋值
RACTupleUnpack(NSString *key,NSString *value) = x;
// 相当于以下写法
// NSString *key = x[0];
// NSString *value = x[1];
NSLog(@"%@ %@",key,value);
}];
RACCommand:RAC 中用于处理事件的类, 可以把事件如何处理, 事件中的数据如何传递, 包装到这个类中, 他可以很方便的监控事件的执行过程.
// 一, RACCommand 使用步骤:
// 1. 创建命令 initWithSignalBlock:(RACSignal * (^)(id input))signalBlock
// 2. 在 signalBlock 中, 创建 RACSignal, 并且作为 signalBlock 的返回值
// 3. 执行命令 - (RACSignal *)execute:(id)input
// 二, RACCommand 使用注意:
// 1.signalBlock 必须要返回一个信号, 不能传 nil.
// 2. 如果不想要传递信号, 直接创建空的信号[RACSignal empty];
// 3.RACCommand 中信号如果数据传递完, 必须调用[subscriber sendCompleted], 这时命令才会执行完毕, 否则永远处于执行中.
// 4.RACCommand 需要被强引用, 否则接收不到 RACCommand 中的信号, 因此 RACCommand 中的信号是延迟发送的.
// 三, RACCommand 设计思想: 内部 signalBlock 为什么要返回一个信号, 这个信号有什么用.
// 1. 在 RAC 开发中, 通常会把网络请求封装到 RACCommand, 直接执行某个 RACCommand 就能发送请求.
// 2. 当 RACCommand 内部请求到数据的时候, 需要把请求的数据传递给外界, 这时候就需要通过 signalBlock 返回的信号传递了.
// 四, 如何拿到 RACCommand 中返回信号发出的数据.
// 1.RACCommand 有个执行信号源 executionSignals, 这个是 signal of signals(信号的信号), 意思是信号发出的数据是信号, 不是普通的类型.
// 2. 订阅 executionSignals 就能拿到 RACCommand 中返回的信号, 然后订阅 signalBlock 返回的信号, 就能获取发出的值.
// 五, 监听当前命令是否正在执行 executing
// 六, 使用场景, 监听按钮点击, 网络请求
// 1. 创建命令
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
NSLog(@"执行命令");
// 创建空信号, 必须返回信号
// return [RACSignal empty];
// 2. 创建信号, 用来传递数据
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"请求数据"];
// 注意: 数据传递完, 最好调用 sendCompleted, 这时命令才执行完毕.
[subscriber sendCompleted];
return nil;
}];
}];
// 强引用命令, 不要被销毁, 否则接收不到数据
_loginCommand = command;
// 3. 订阅 RACCommand 中的信号
[command.executionSignals subscribeNext:^(id x) {
// 订阅 executionSignals 就能拿到 RACCommand 中返回的信号, 然后订阅 signalBlock 返回的信号, 就能获取发出的值.
[x subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
}];
// RAC 高级用法
// switchToLatest: 用于 signal of signals, 获取 signal of signals 发出的最新信号, 也就是可以直接拿到 RACCommand 中的信号
/* [command.executionSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"%@",x);
}];*/
// 4. 监听命令是否执行完毕, 默认会来一次, 可以直接跳过, skip 表示跳过第一次信号.
[[command.executing skip:1] subscribeNext:^(id x) {
// executing: 判断当前的 block 是否在执行, 执行完之后会返回 NO
if ([x boolValue] == YES) {
// 正在执行
NSLog(@"正在执行");
}else{
// 执行完成
NSLog(@"执行完成");
}
}];
// 5. 执行命令(控制器里执行此句代码)
[self.login_vm.loginCommand execute:nil];
RACMulticastConnection
: 用于当一个信号, 被多次订阅时, 为了保证创建信号时, 避免多次调用创建信号中的 block, 造成副作用, 可以使用这个类处理.
使用注意: RACMulticastConnection 通过 RACSignal 的 - publish 或者 - muticast: 方法创建.
RACScheduler:RAC 中的队列, 用 GCD 封装的.
RACUnit : 表 stream 不包含有意义的值, 也就是看到这个, 可以直接理解为 nil.
四, RAC 常用宏
1,RAC 绑定一个信号
RAC 宏允许直接把信号的输出应用到对象的属性上 每次信号产生一个 next 事件, 传递过来的值都会应用到该属性上
//1,RAC 把一个对象的摸个属性绑定一个信号, 只有发出信号, 就会吧信号的内容给对象的属性赋值.
// 这里吧 label 的 text 属性绑定到 textField 改变信号中, textfield 的内容发生改变的时候就会发出信号, 只要文本框文字改变, 就会修改 label 的文字.
RAC(self.label,text) = _textfield.rac_textSignal;
2,RACObserve 相当于 kvo 使用
//person 为一个模型, 只要模型里面的 name 字段改变, 就会把最新的 name 值显示在 label 上
[RACObserve(self.person, name) subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
self.nameLabel.text = x;
}];
3,@weakify 和 @strongify 解决循环引用, 注意这两个宏是配套使用才有效.
@weakify(self);
[[self.testTextFileld rac_textSignal] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"%@",x);
@strongify(self);
self.label.text = x;
}];
4, RACChannelTo 用于双向绑定
也可以用两个信号互相订阅(subscribe:)
5,RACTuplePack: 把数据包装成 RACTuple(元组类)
// 把参数中的数据包装成元组
RACTuple *tuple = RACTuplePack(@(1),@(30));
6,RACTupleUnpack: 把 RACTuple(元组类)解包成对应的数据.
// 把参数中的数据包装成元组
RACTuple *tuple = RACTuplePack(@"xmg",@20);
// 解包元组, 会把元组的值, 按顺序给参数里面的变量赋值
// name = @"xmg" age = @20
RACTupleUnpack(NSString *name,NSNumber *age) = tuple;
五, 常见操作方法
所有的信号 (RACSignal) 都可以进行操作处理, 因为所有操作方法都定义在 RACStream.h 中, 因此只要继承 RACStream 就有了操作处理方法.
1. bind(绑定) 核心方法
ReactiveCocoa 操作的核心方法是 bind(绑定), 而且也是 RAC 中核心开发方式. 之前的开发方式是赋值, 而用 RAC 开发, 应该把重心放在绑定, 也就是可以在创建一个对象的时候, 就绑定好以后想要做的事情, 而不是等赋值之后在去做事情.
RAC 底层都是调用 bind, 在开发中很少直接使用 bind 方法, bind 属于 RAC 中的底层方法, 我们只需要调用封装好的方法, bind 用作了解即可.
2. flattenMap
把源信号的内容映射成一个新的信号, 信号可以是任意类型
使用步骤:
1. 传入一个 block,block 类型是返回值 RACStream, 参数 value
2. 参数 value 就是源信号的内容, 拿到源信号的内容做处理
3. 包装成 RACReturnSignal 信号, 返回出去
// 监听文本框的内容改变, 重新映射成一个新的信号
[[_textField.rac_textSignal flattenMap:^RACStream *(id value) {
// block 调用时机: 信号源发出的时候
// block 作用: 改变信号的内容
// 返回 RACReturnSignal (需要导入 RACReturnSignal.h)
return [RACReturnSignal return:[NSString stringWithFormat:@"信号内容:%@", value]];
}] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
3. Map
把源信号的值映射成一个新的值
如果 map 的是一个 sequence, 那么会遍历里面的么一个元素, 遍历的每一个值, 都会做相同的处理
// 监听文本框的内容改变, 映射成一个新值.
[[_textField.rac_textSignal map:^id(id value) {
// 把处理好的内容, 直接返回就好了, 不用包装成信号, 返回的值, 就是映射的值.
return [NSString stringWithFormat:@"信号内容: %@", value];
}] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
FlatternMap 和 Map 的区别
FlatternMap 中的 Block 返回信号.
Map 中的 Block 返回对象.
开发中, 如果信号发出的值 不是信号 , 映射一般使用 Map
如果信号发出的值 是信号, 映射一般使用 FlatternMap.
信号中的信号 (signalOfsignals) 用 flatternMap:
// 创建信号中的信号
RACSubject *signalOfsignals = [RACSubject subject];
RACSubject *signal = [RACSubject subject];
[[signalOfsignals flattenMap:^RACStream *(id value) {
// 当 signalOfsignals 的 signals 发出信号才会调用
return value;
}] subscribeNext:^(id x) {
// 只有 signalOfsignals 的 signal 发出信号才会调用, 因为内部订阅了 bindBlock 中返回的信号, 也就是 flattenMap 返回的信号.
// 也就是 flattenMap 返回的信号发出内容, 才会调用.
NSLog(@"signalOfsignals:%@",x);
}];
// 信号的信号发送信号
[signalOfsignals sendNext:signal];
// 信号发送内容
[signal sendNext:@"hi"];
------- 组合 -------
组合就是将多个信号按照某种规则进行拼接, 合成新的信号.
4. concat
按顺序拼接信号, 当多个信号发出的时候, 有顺序的接收信号.
// 拼接信号 signalA, signalB, signalC
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"Hello"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"World"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalC = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"!"];
[subscriber sendCompleted];
return nil;
}];
// 拼接 A B, 把 signalA 拼接到 signalB 后, signalA 发送完成, signalB 才会被激活.
RACSignal *concatSignalAB = [signalA concat:signalB];
// A B + C
RACSignal *concatSignalABC = [concatSignalAB concat:signalC];
// 订阅拼接的信号, 内部会按顺序订阅 A->B->C
// 注意: 第一个信号必须发送完成, 第二个信号才会被激活...
[concatSignalABC subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
5. then
用于连接两个信号, 当第一个信号完成, 才会连接 then 返回的信号.
then 方法会等待 completed 事件的发送, 然后再订阅由 then block 返回的 signal. 这样就高效地把控制权从一个 signal 传递给下一个.
注意使用 then, 之前信号的值会被忽略掉
6. merge
合并信号, 任何一个信号发送数据, 都能监听到.
合并信号被订阅的时候, 就会遍历所有信号, 并且发出这些信号.
合并信号一被订阅, 就会订阅里面所有的信号.
只要有一个信号被发出就会被监听.
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"A"];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"B"];
return nil;
}];
// 合并信号, 任何一个信号发送数据, 都能监听到
RACSignal *mergeSianl = [signalA merge:signalB];
[mergeSianl subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
// 输出
//A
//B
7. combineLatest
将多个信号合并起来, 并且拿到各个信号最后一个值, 必须每个合并的 signal 至少都有过一次 sendNext, 才会触发合并的信号.
必须两个信号都发出内容, 才会被触发, 并且把两个信号的 最后一次 发送的值组合成元组发出.
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"A1"];
[subscriber sendNext:@"A2"];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"B1"];
[subscriber sendNext:@"B2"];
[subscriber sendNext:@"B3"];
return nil;
}];
RACSignal *combineSianal = [signalA combineLatestWith:signalB];
[combineSianal subscribeNext:^(id x) {
NSLog(@"combineLatest:%@", x);
}];
/*
输出
2018-02-01 17:39:04.914804+0800 RAC 101[21958:29964626] combineLatest:<RACTwoTuple: 0x60400000fc70> (
A2,
B1
)
2018-02-01 17:39:04.915092+0800 RAC 101[21958:29964626] combineLatest:<RACTwoTuple: 0x60000000faa0> (
A2,
B2
)
2018-02-01 17:39:04.915270+0800 RAC 101[21958:29964626] combineLatest:<RACTwoTuple: 0x60000000fff0> (
A2,
B3
)
*/
// 如果用[signalB combineLatestWith:signalA], 那么结果就是(B3, A1) (B3, A2)
8. reduce
把信号发出元组的值聚合成一个值
reduce 合并信号的数据, 进行汇总计算使用
订阅聚合信号, 每次有内容发出, 就会执行 reduceblcok, 把信号内容转换成 reduceblcok 返回的值.
常见的用法,(先组合在聚合)一般跟 combineLatest 一起使用
// 判断用户名和密码同时存在的时候, 按钮才能点击
[[RACSignal combineLatest:@[self.nameField.rac_textSignal, self.passwordField.rac_textSignal] reduce:^id _Nullable(NSString *name, NSString *pwd){
return @(name.length > 0 && pwd.length > 0);
}] subscribeNext:^(id _Nullable x) {
_btn.enabled = [x boolValue];
}];
9. zip
把两个信号压缩成一个信号, 只有当两个信号 同时 发出信号内容时, 并且把两个信号的内容合并成一个元组, 才会触发压缩流的 next 事件.
1. 定义压缩信号, 内部就会自动订阅 signalA,signalB
2. 每当 signalA 或者 signalB 发出信号, 就会判断 signalA,signalB 有没有发出个信号, 有就会把每个信号 第一次 发出的值包装成元组发出
注意: combineLatest 与 zip 用法相似, 必须每个合并的 signal 至少都有过一次 sendNext, 才会触发合并的信号.
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"A1"];
[subscriber sendNext:@"A2"];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"B1"];
[subscriber sendNext:@"B2"];
[subscriber sendNext:@"B3"];
return nil;
}];
RACSignal *zipSignal = [signalA zipWith:signalB];
[zipSignal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
/*
结果:
2018-02-01 18:00:04.585460+0800 RAC 101[22558:30088028] <RACTwoTuple: 0x604000005840> (
A1,
B1
)
2018-02-01 18:00:04.585790+0800 RAC 101[22558:30088028] <RACTwoTuple: 0x6040000058d0> (
A2,
B2
)
*/
10. filter
过滤信号, 使用它可以获取满足条件的信号
// 每次信号发出, 会先执行过滤条件判断.
[[_textField.rac_textSignal filter:^BOOL(NSString *value) {
NSLog(@"原信号: %@", value);
// 过滤 长度 <= 3 的信号
return value.length > 3;
}] subscribeNext:^(id x) {
NSLog(@"长度大于 3 的信号:%@", x);
}];
11. ignore
忽略某些信号
底层调用了 filter 与 过滤值进行比较, 若相等返回则 NO
// 过滤掉值为 111 的信号
[[_textField.rac_textSignal ignore:@"111"] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
12. distinctUntilChanged
当上一次的值和当前的值有明显的变化就会发出信号, 否则会被忽略掉.
[[_textField.rac_textSignal distinctUntilChanged] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
13. skip
跳过第 N 次的发送的信号.
// 表示输入第一次, 不会被监听到, 跳过第一次发出的信号
[[_textField.rac_textSignal skip:1] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
14. take
取前 N 次的发送的信号.
RACSubject *subject = [RACSubject subject] ;
// 取 前两次 发送的信号
[[subject take:2] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@3];
// 输出
2017-01-03 17:35:54.566 ReactiveCocoa 进阶[4969:1677908] 1
2017-01-03 17:35:54.567 ReactiveCocoa 进阶[4969:1677908] 2
15. takeLast
取最后 N 次的发送的信号
前提条件, 订阅者必须调用完成 sendCompleted, 因为只有完成, 就知道总共有多少信号.
RACSubject *subject = [RACSubject subject] ;
// 取 后两次 发送的信号
[[subject takeLast:2] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@3];
// 必须 跳用完成
[subject sendCompleted];
16. takeUntil
获取信号直到某个信号执行完成
// 监听文本框的改变直到当前对象被销毁
[_textField.rac_textSignal takeUntil:self.rac_willDeallocSignal];
17. switchToLatest
用于 signalOfSignals(信号的信号), 有时候信号也会发出信号, 会在 signalOfSignals 中, 获取 signalOfSignals 发送的最新信号.
switchToLatest: 只能用于信号中的信号
RACSubject *signalOfSignals = [RACSubject subject];
RACSubject *signal = [RACSubject subject];
// 获取信号中信号最近发出信号, 订阅最近发出的信号.
[signalOfSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
[signalOfSignals sendNext:signal];
[signal sendNext:@1];
---- 秩序 ----
秩序包括 doNext 和 doCompleted 这两个方法, 主要是在 执行 sendNext 或者 sendCompleted 之前, 先执行这些方法中 Block.
18. doNext
执行 sendNext 之前, 会先执行这个 doNext 的 Block
19. doCompleted
执行 sendCompleted 之前, 会先执行这 doCompleted 的 Block
[[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"hi"];
[subscriber sendCompleted];
return nil;
}] doNext:^(id x) {
// 执行 [subscriber sendNext:@"hi"] 之前会调用这个 Block
NSLog(@"doNext");
}] doCompleted:^{
// 执行 [subscriber sendCompleted] 之前会调用这 Block
NSLog(@"doCompleted");
}] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
/*
运行结果:
2018-02-02 09:56:39.206710+0800 RAC 101[40487:30848792] doNext
2018-02-02 09:56:39.206954+0800 RAC 101[40487:30848792] hi
2018-02-02 09:56:39.207112+0800 RAC 101[40487:30848792] doCompleted
*/
---- 线程 ----
ReactiveCocoa 中的线程操作 包括 deliverOn 和 subscribeOn 这两种, 将 传递的内容 或 创建信号时 block 中的代码 切换到指定的线程中执行.
20. deliverOn
内容传递切换到指定线程中, 副作用在原来线程中, 把在创建信号时 block 中的代码称之为副作用.
// 在子线程中执行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"%@", [NSThread currentThread]);
[subscriber sendNext:@123];
[subscriber sendCompleted];
return nil;
}]
deliverOn:[RACScheduler mainThreadScheduler]]
subscribeNext:^(id x) {
NSLog(@"%@", x);
NSLog(@"%@", [NSThread currentThread]);
}];
});
/*
运行结果:
2018-02-02 14:10:28.938587+0800 RAC 101[45182:31032478] <NSThread: 0x604000279c00>{number = 3, name = (null)}
2018-02-02 14:10:29.076247+0800 RAC 101[45182:31031131] 123
2018-02-02 14:10:29.076494+0800 RAC 101[45182:31031131] <NSThread: 0x60400006f540>{number = 1, name = main}
*/
可以看到 副作用 在 子线程 中执行, 而 传递的内容 在 主线程 中接收
21. subscribeOn
subscribeOn 则是将 内容传递 和 副作用 都会切换到指定线程中.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"%@", [NSThread currentThread]);
[subscriber sendNext:@123];
[subscriber sendCompleted];
return nil;
}]
subscribeOn:[RACScheduler mainThreadScheduler]] // 传递的内容到主线程中
subscribeNext:^(id x) {
NSLog(@"%@", x);
NSLog(@"%@", [NSThread currentThread]);
}];
});
/*
运行结果:
2018-02-02 14:15:23.956524+0800 RAC 101[45319:31066198] <NSThread: 0x600000079940>{number = 1, name = main}
2018-02-02 14:15:23.956737+0800 RAC 101[45319:31066198] 123
2018-02-02 14:15:23.956961+0800 RAC 101[45319:31066198] <NSThread: 0x600000079940>{number = 1, name = main}
*/
可以看到, 内容传递 和 副作用 都切换到了 主线程 执行
---- 时间 ----
时间操作就会设置信号超时, 定时和延时.
22. interval
定时: 每隔一段时间发出信号
// 每隔 1 秒发送信号, 指定当前线程执行
[[RACSignal interval:1 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id x) {
NSLog(@"定时:%@", x);
}];
23. timeout
超时, 可以让一个信号在一定的时间后, 自动报错.
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 不发送信号, 模拟超时状态
// [subscriber sendNext:@"hello"];
//[subscriber sendCompleted];
return nil;
}] timeout:1 onScheduler:[RACScheduler currentScheduler]];// 设置 1 秒超时
[signal subscribeNext:^(id x) {
NSLog(@"%@", x);
} error:^(NSError *error) {
NSLog(@"%@", error);
}];
// 执行代码 1 秒后 输出:
2017-01-04 13:48:55.195 ReactiveCocoa 进阶[1980:492724] Error Domain=RACSignalErrorDomain Code=1 "(null)"
24. retry
重试: 只要 发送错误 sendError: 就会重新执行创建信号的 Block 直到成功
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
if (i == 3) {
[subscriber sendNext:@"Hello"];
} else {
// 发送错误
NSLog(@"收到错误:%d", i);
[subscriber sendError:nil];
}
i++;
return nil;
}] retry] subscribeNext:^(id x) {
NSLog(@"%@", x);
} error:^(NSError *error) {
NSLog(@"%@", error);
}];
// 输出
2017-01-04 14:36:51.594 ReactiveCocoa 进阶[2443:667226] 收到错误信息: 0
2017-01-04 14:36:51.595 ReactiveCocoa 进阶[2443:667226] 收到错误信息: 1
2017-01-04 14:36:51.595 ReactiveCocoa 进阶[2443:667226] 收到错误信息: 2
2017-01-04 14:36:51.596 ReactiveCocoa 进阶[2443:667226] Hello
25. replay
重放: 当一个信号被多次订阅, 反复播放内容
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
[subscriber sendNext:@2];
return nil;
}] replay];
[signal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
[signal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
/*
运行结果:
2018-02-02 14:37:45.269758+0800 RAC 101[45938:31201667] 1
2018-02-02 14:37:45.270018+0800 RAC 101[45938:31201667] 2
2018-02-02 14:37:45.270175+0800 RAC 101[45938:31201667] 1
2018-02-02 14:37:45.270300+0800 RAC 101[45938:31201667] 2
/*
26. throttle
节流: 当某个信号发送比较频繁时, 可以使用节流, 在某一段时间不发送信号内容, 过了一段时间获取信号的最新内容发出.
RACSubject *subject = [RACSubject subject];
// 节流 1 秒, 1 秒后接收最后一个发送的信号
[[subject throttle:1] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@3];
// 输出
2018-02-02 14:53:00.234975+0800 RAC 101[46278:31282576] 3
扩展: 利用 RAC 封装网络请求
通过 RAC, 我们可以奖网络请求封装为信号的形式, 数据的获取可以通过订阅信号的方式来得到. 与传统的网络请求相比, 是不是感觉耳目一新?
#import <AFNetworking/AFNetworking.h>
#import "AFNetworking.h"
#import <ReactiveObjC.h>
@interface NetworkingManager : AFHTTPSessionManager
+ (instancetype)shareManager;
- (RACSignal *)GET:(NSString *)url parameters:(id)parameters;
@end
--------------------------------------------------------------------
@implementation NetworkingManager
+ (instancetype)shareManager
{
static NetworkingManager *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[self alloc]init];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript",@"text/html" ,nil];
});
return manager;
}
//GET 请求
- (RACSignal *)GET:(NSString *)url parameters:(id)parameters
{
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[self GET:url parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[subscriber sendNext:responseObject];
[subscriber sendCompleted];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[subscriber sendError:error];
}];
return nil;
}];
}
@end
--------------------------------------------------------------------
// 请求网络数据
- (void)fetchWeatherData
{
NetworkingManager *manager = [NetworkingManager shareManager];
[[manager GET:@"http://www.weather.com.cn/data/sk/101010100.html" parameters:nil] subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
} error:^(NSError * _Nullable error) {
NSLog(@"%@", error.localizedDescription);
}];
}
// 如上: 调用请求, 订阅信号后得到的 x 就是需要的 json 数据.
来源: http://www.jianshu.com/p/522e90685b37