RAC知识点
- RACSignal发送事件流给它的subscribe.目前总共有三种类型的事件:next error completed.一个signal在因error终止或者完成前,可以发送任意数量的next事件
- RACSignal的每个操作都会返回一个RACSignal(返回的信号是新创建的,与上一个信号不是相同实例),这叫做连贯接口(fluent interface).这个功能可以让你直接构建管道,而不用每一步都使用本地变量
- map: 接收上一次的next事件返回值,可以根据需要修改该值,并把修改后的值传递给下一个next事件
- ==filter==: 根据接收的事件返回值,判断是否继续往后传递事件
- ==subscribeNext==: 事件的订阅者,每次next事件发生时,subscribeNext:方法提供的block都会执行
- ==RAC宏==允许直接把信号的输出应用到对象的属性上.RAC宏有两个参数,第一个是需要设置属性值得对象.第二个是属性名.每次信号产生一个Next事件,传递过来的值都会应用到该属性上
- ==RACObserve宏==监听属性变化的RACObserve(TARGET, KEYPATH)// ==TARGET==:监听目标 ==KEYPATH==:目标属性
聚合信号
- RACSignal的这个方法可以聚合任意数量的信号,reduce block的参数和每个源信号相关.ReactiveCoco有一个工具类RACBlockTrampoline,它在内部处理reduce block的可变参数
代码示例:
+ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock
文中实例
[RACSignal combineLatest:@[validUsernameSignal, validPasswordSignal]
reduce:^id(NSNumber *usernameValid, NSNumber *passwordValid) {
return @([usernameValid boolValue] && [passwordValid boolValue]);
}];
注:这个聚合信号如果没有订阅者,不会执行reduce的block。
- 一个信号可以有多个订阅者(分隔),多个信号可以聚合为一个新的信号.
控件事件
- 文本事件 rac_textSignal
- 按钮事件 rac_signalForControlEvents
副作用(Adding side-effects)
- doNext:用于执行一些事情发生时的附加操作.不影响事件本身,如UI变化,它的block无返回值.
- flattenMap 用于将当前外部信号中的内部信号的事件传递给下一个外部信号
ReactiveCocoa
==概念==
- ==Reactive==:响应式的、函数式的
- ==Cocoa==:苹果开发框架名称
- ==Reactive+Cocoa==:具备函数式编程思想的开发框架
==为什么要用ReactiveCocoa==
- 统一简化了苹果原生框架大多场景,提高开发效率
- 统一ios原生中常用的Target、Delegate、KVO、通知、NSTimer、网络异步回调等操作,并将响应的事件处理代码聚合一起
第一:基础概念
-
核心是携带信息的信号进行传递和订阅的过程
- 示意图: image
-
与之相关的四个大的概念
- 1.信号源
- 2.订阅者
- 3.回收站(RACDisposable)
- 4.调度器
1.信号源
-
信号的老祖宗RACStream,指的是信号流,其继承结构如图:
image
==通常情况下不直接使用RACStream,而是使用其子类进行信号传递==
-
核心类RACSignal
- 最常用的信号类,.主要功能是创建信号、配置订阅者来订阅信号
- 信号在传递过程中包括正常传递、传递完成、传递失败的状态,分别为:Next、completed、error
-
如图:
image
-
序列信号RACSequence
- 简化OC中的集合操作的信号类
2.订阅者
- 为了获取某个信号源中的值,通常需要对该信号源进行订阅,这就产生了订阅者的概念
- 在ReactiveCocoa中所有实现了RACSubscriber协议的类都可以作为信号源的订阅者
- RACSubscribe协议规定实现的四个方法:
@protocol RACSubscriber <NSObject>
@required
- (void)sendNext:(id)value;
- (void)sendError:(NSError *)error;
- (void)sendCompleted;
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable;
@end
- 其中 ==-sendNext:==、==-sendError:== 和 ==-sendComplete==方法分别是接收信号Next、error和complete事件; ==-didSubscribeWithDIsposable:== 方法用来接收代表某次订阅的RACDisposable(回收站)对象
3.回收站(RACDisposable)
- 在ReactiveCocoa中并没有专门的类来代表一次完整的订阅行为,而间接地使用RACDisposable来表示订阅行为。
- 订阅完成或失败时自动触发回收机制,因为它主要做一些资源回收和垃圾清理的工作。
- 可以通过RACDisposable的 ==dispose== 方法主动取消订阅行为,不再接受信号传递的信息
4.调度器(RACScheduler)
- RACScheduler是对GCD的简单封装,用来调度订阅任务
第二:核心信号之RACSignal
- 创建、订阅、发送、回收信号
- 代码示例:
- (void)testCreateSignal {
// 1.创建信号
RACSignal *siganl = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 每当有订阅者订阅信号,就会调用此block。
// 3.发送信号
[subscriber sendNext:@"糖水"];
[subscriber sendNext:@"盐水"];
[subscriber sendNext:@"辣椒水"];
// 如果不再发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
// 5.订阅完成
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
// 7.当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
// 执行完Block后,当前信号就不在被订阅了。
NSLog(@"信号被销毁");
}];
}];
// 2.订阅信号,依次获取步骤3中传递的信息
// (此方法隐藏了两件事:一是创建订阅者RACSubscriber,二是为信号配置订阅者并触发订阅任务。其目的是简化信号订阅逻辑)
[siganl subscribeNext:^(id x) {
// 4.每当有信号sendNext: ,就会调用此block.
NSLog(@"接收到数据:%@",x);
} error:^(NSError *error) {
NSLog(@"信号发送失败");
} completed:^{
// 6.第5步中[subscriber sendCompleted];执行后直接走此处方法
NSLog(@"信号发送完成");
}];
// 8.继续订阅信号,依次获取步骤3中传递的信息
[siganl subscribeNext:^(id x) {
// 每当有信号sendNext: ,就会调用此block.
NSLog(@"接收到数据:%@",x);
}];
}
// 最终控制台输出结果为:
2017-06-23 16:15:49.027 RACDemo[48262:2367413] 第一个订阅者接收到:糖水
2017-06-23 16:15:49.027 RACDemo[48262:2367413] 第一个订阅者接收到:盐水
2017-06-23 16:15:49.027 RACDemo[48262:2367413] 第一个订阅者接收到:辣椒水
2017-06-23 16:15:49.028 RACDemo[48262:2367413] 信号发送完成
2017-06-23 16:15:49.028 RACDemo[48262:2367413] 信号被销毁
2017-06-23 16:15:49.028 RACDemo[48262:2367413] 第二个订阅者接收到:糖水
2017-06-23 16:15:49.028 RACDemo[48262:2367413] 第二个订阅者接收到:盐水
2017-06-23 16:15:49.028 RACDemo[48262:2367413] 第二个订阅者接收到:辣椒水
2017-06-23 16:15:49.029 RACDemo[48262:2367413] 信号被销毁
==解读==
-
对于信号创建、订阅、发送、回收,我们可以把整个ReactiveCocoa框架形象的类比为水管管道机制,如下图:
image
华丽的信号家族之RACSubject
- 上图描述的是一般的信号传递机制,华丽版的信号传递无非是在此基础上,修改,调整水阀之间的连通关系
1.RACSubject
- RACSubject与RACSignal做大不同的是,RACSubject既是信号的子类,同时也能实现订阅信号着的RACSubscriber协议,因此也可以充当订阅者发送消息
- 示例代码:
- (void)testRACSubject {
// RACSubject使用步骤
// 1.创建信号 [RACSubject subject],跟RACSiganl不一样,创建信号时没有block。
// 2.订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
// 3.发送信号 sendNext:(id)value
// RACSubject:底层实现和RACSignal不一样。
// 1.调用subscribeNext订阅信号,只是把订阅者保存起来,并且订阅者的nextBlock已经赋值了。
// 2.调用sendNext发送信号,遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock。
// 1.创建信号
RACSubject *subject = [RACSubject subject];
// 2.订阅信号
[subject subscribeNext:^(id x) {
// block调用时刻:当信号发出新值,就会调用.
NSLog(@"第一个订阅者%@",x);
}];
[subject subscribeNext:^(id x) {
// block调用时刻:当信号发出新值,就会调用.
NSLog(@"第二个订阅者%@",x);
}];
// 3.发送信号
[subject sendNext:@"糖水"];
[subject sendNext:@"盐水"];
[subject sendNext:@"辣椒水"];
}
// 最终控制台输出结果为:
2017-06-23 16:12:13.565 RACDemo[48194:2363636] 第一个订阅者接收到:糖水
2017-06-23 16:12:13.566 RACDemo[48194:2363636] 第二个订阅者接收到:糖水
2017-06-23 16:12:13.566 RACDemo[48194:2363636] 第一个订阅者接收到:盐水
2017-06-23 16:12:13.566 RACDemo[48194:2363636] 第二个订阅者接收到:盐水
2017-06-23 16:12:13.566 RACDemo[48194:2363636] 第一个订阅者接收到:辣椒水
2017-06-23 16:12:13.566 RACDemo[48194:2363636] 第二个订阅者接收到:辣椒水
==解读==
- 首先写法上, ==sendNext:== 不用挤在信号创建的block中写,RACSubject对象本身可以作为订阅者调用此方法
- 其次RACSubject的订阅方法( ==subscribeNext:== )仅仅是接收者
- ==sendNext:== 会把信号传递给所有的 ==subscribeNext:==
-
RACSubject和RACSignal最大的不同点在于发送信号的时机,前者是配置好发送信号的出口,如图:
image
2.RACReplaySubject
- 代码:
- (void)testRACReplaySubject {
// RACReplaySubject:底层实现和RACSubject不一样。
// 1.调用sendNext发送信号,把值保存起来,然后遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock。
// 2.调用subscribeNext订阅信号,遍历保存的所有值,一个一个调用订阅者的nextBlock
// 如果想当一个信号被订阅,就重复播放之前所有值,需要先发送信号,在订阅信号。
// 也就是先保存值,在订阅值。
// 1.创建信号
RACReplaySubject *replaySubject = [RACReplaySubject subject];
// 2.发送信号
[replaySubject sendNext:@"糖水"];
[replaySubject sendNext:@"盐水"];
[replaySubject sendNext:@"辣椒水"];
// 3.订阅信号
[replaySubject subscribeNext:^(id x) {
NSLog(@"第一个订阅者接收到:%@",x);
}];
// 订阅信号
[replaySubject subscribeNext:^(id x) {
NSLog(@"第一个订阅者接收到:%@",x);
}];
}
// 最终控制台输出结果为:
2017-06-26 17:52:39.670 RACDemo[61435:3788527] 第一个订阅者接收到:糖水
2017-06-26 17:52:39.671 RACDemo[61435:3788527] 第一个订阅者接收到:盐水
2017-06-26 17:52:39.671 RACDemo[61435:3788527] 第一个订阅者接收到:辣椒水
2017-06-26 17:52:39.671 RACDemo[61435:3788527] 第二个订阅者接收到:糖水
2017-06-26 17:52:39.671 RACDemo[61435:3788527] 第二个订阅者接收到:盐水
2017-06-26 17:52:39.672 RACDemo[61435:3788527] 第二个订阅者接收到:辣椒水
==解读==
-
输出的结果跟RACSignal完全一致,只是写法上略有不同
-
继承RACSubject的RACReplaySubject实现了基本信号功能
-
RACReplaySubject还可以在上面的订阅代码后面,继承发送信号 ==sendNext:== ,实现和RACSubject一样的功能
-
如图所示:
image -
总结:RACSubject实现每个信号群到所有信号发送口,RACReplaySubject实现每个信号发送口接收所有的信号
3.RACMulticastConnection
- 代码示例:
- (void)testRACMulticastConnection {
// RACMulticastConnection使用步骤:
// 1.创建信号 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
// 2.创建连接 RACMulticastConnection *connect = [signal publish];
// 3.订阅信号,注意:订阅的不在是之前的信号,而是连接的信号。 [connect.signal subscribeNext:nextBlock]
// 4.连接 [connect connect]
// RACMulticastConnection底层原理:
// 1.创建connect,connect.sourceSignal -> RACSignal(原始信号) connect.signal -> RACSubject
// 2.订阅connect.signal,会调用RACSubject的subscribeNext,创建订阅者,而且把订阅者保存起来,不会执行block。
// 3.[connect connect]内部会订阅RACSignal(原始信号),并且订阅者是RACSubject
// 3.1.订阅原始信号,就会调用原始信号中的didSubscribe
// 3.2 didSubscribe,拿到订阅者调用sendNext,其实是调用RACSubject的sendNext
// 4.RACSubject的sendNext,会遍历RACSubject所有订阅者发送信号。
// 4.1 因为刚刚第二步,都是在订阅RACSubject,因此会拿到第二步所有的订阅者,调用他们的nextBlock
// 需求:假设在一个信号中发送请求,每次订阅一次都会发送请求,这样就会导致多次请求。
// 解决:使用RACMulticastConnection就能解决.
// RACMulticastConnection:解决重复请求问题
// 1.创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"发送请求");
[subscriber sendNext:@"糖水"];
[subscriber sendNext:@"盐水"];
[subscriber sendNext:@"辣椒水"];
return nil;
}];
// 2.创建连接
RACMulticastConnection *connect = [signal publish];
// 3.订阅信号,
// 注意:订阅信号,也不能激活信号,只是保存订阅者到数组,必须通过连接,当调用连接,就会一次性调用所有订阅者的sendNext:
[connect.signal subscribeNext:^(id x) {
NSLog(@"第一个订阅者接收到:%@",x);
}];
[connect.signal subscribeNext:^(id x) {
NSLog(@"第二个订阅者接收到:%@",x);
}];
// 4.连接,激活信号
[connect connect];
}
// 最终控制台输出结果为:
2017-06-26 18:44:55.057 RACDemo[61893:3825826] 发送请求
2017-06-26 18:44:55.058 RACDemo[61893:3825826] 第一个订阅者接收到:糖水
2017-06-26 18:44:55.058 RACDemo[61893:3825826] 第二个订阅者接收到:糖水
2017-06-26 18:44:55.058 RACDemo[61893:3825826] 第一个订阅者接收到:盐水
2017-06-26 18:44:55.058 RACDemo[61893:3825826] 第二个订阅者接收到:盐水
2017-06-26 18:44:55.058 RACDemo[61893:3825826] 第一个订阅者接收到:辣椒水
2017-06-26 18:44:55.059 RACDemo[61893:3825826] 第二个订阅者接收到:辣椒水
==解读==
- RACMulticastConnection的庐山真面目就是RACSubject订阅机制
第四:封装好的简单信号
- 1)监听是否实现了某个方法之 ==rac_signalForSelector==
- 应用场景:判断某个代理是否实现某方法,或者监控某个按钮所在VC是否实现了点击方法
if ([self rac_signalForSelector:@selector(clickTestButton:)]) {
NSLog(@"点击了测试按钮");
}
- 2)替代KVO的 ==rac_valuesAndChangesForPath==
[[self rac_valuesAndChangesForKeyPath:@"testNum"
options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
NSLog(@"new value info is %@", x);
}];
- 3)监听事件 ==rac_signalForControlEvents==,如点击事件.
[[self.testButton rac_signalForControlEvents:UIControlEventTouchUpInside]
subscribeNext:^(id x) {
NSLog(@"已经实现点击事件");
}];
- 4)监听通知 ==rac_addObserverForName== ,如键盘弹起的通知
[[[NSNotificationCenter defaultCenter]
rac_addObserverForName:UIKeyboardWillShowNotification object:nil]
subscribeNext:^(id x) {
NSLog(@"键盘弹出");
}];
- 5)手势信号之 ==rac_gestureSignal==
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] init];
[panGesture.rac_gestureSignal subscribeNext:^(id x) {
NSLog(@"拖动手势:%@", x);
}];
[self.view addGestureRecognizer:panGesture];
- 6)UITextfield或UITextView文字信号之 ==rac_textSignal==
[_textField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"当前文字:%@", x);
}];
第五: 升级篇
- RACSequence
- RACCommand
- RACSchedule
- RACChannel
- 信号转换
- 信号拼接
- RAC常用宏
1.RACSequence
- 为了简化OC代码中的集合类操作
- 代码示例:
- (void)testRACSequence {
// 1.遍历数组
NSArray *numbers = @[@1, @2, @3, @4];
// 这里其实是三步
// 第一步: 把数组转换成集合RACSequence,即numbers.rac_sequence
// 第二步: 把集合RACSequence转换RACSignal信号类,numbers.rac_sequence.signal
// 第三步: 订阅信号,激活信号,会自动把集合中的所有值,遍历出来。
[numbers.rac_sequence.signal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 2.遍历字典,遍历出来的键值对会包装成RACTuple(元组对象)
NSDictionary *dict = @{@"name":@"ABC",@"age":@22};
// 下面RACTuple元组的概念请自行查阅
[dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
// 解包元组,会把元组的值,按顺序给参数里面的变量赋值
RACTupleUnpack(NSString *key,NSString *value) = x;
// 相当于以下写法
// NSString *key = x[0];
// NSString *value = x[1];
NSLog(@"%@ %@", key, value);
}];
2.RACCommand
- RACCommand是一个可以包裹一个信号,并对信号的执行,完成,失败状态进行把控的类,通常把异步网络请求封装成信号的形式,然后通过RACCommand包裹信号,实时反馈网络请求状态(请求中、请求成功、请求失败).
- 模拟网络请求,代理示例:
/// 先在当前VC中添加属性:
@interface ViewController ()
@property (nonatomic, strong) RACCommand *command;
@end
/// 实现文件里写入此示例代码
- (void)testRACCommand {
if (!_command) {
_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"开始请求网络");
// 假装请求到数据
[subscriber sendNext:@"请求到的数据"];
// 请求完成
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
NSLog(@"订阅过程完成");
}];
}];
}];
// 监控是否执行中状态
[_command.executing subscribeNext:^(id x) {
NSNumber *executing = (NSNumber *)x;
NSString *stateStr = executing.boolValue? @"请求状态" : @"未请求状态";
NSLog(@"%@", stateStr);
}];
// 监控请求成功的状态
[_command.executionSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"请求成功后,拿到数据: %@", x);
}];
// 监控请求失败的状态
[_command.errors subscribeNext:^(id x) {
NSLog(@"请求失败: %@", x);
}];
}
// 执行操作 调用上面createSignal中的block操作
[_command execute:nil];
}
打印结果为:
2017-06-27 17:10:21.672 RACDemo[65799:6318742] 未请求状态
2017-06-27 17:10:21.680 RACDemo[65799:6318742] 请求状态
2017-06-27 17:10:21.681 RACDemo[65799:6318742] 开始请求网络
2017-06-27 17:10:21.681 RACDemo[65799:6318742] 请求成功后,拿到数据: 请求到的数据
2017-06-27 17:10:21.682 RACDemo[65799:6318742] 订阅过程完成
2017-06-27 17:10:21.682 RACDemo[65799:6318742] 未请求状态
==解读==
- 这里RACCommand的创建使用 ==initWithSignalBlock:== 方法,其参数带一个参数并返回信号的block,因此直接在后面返回一个信号即可
- 创建完RACCommand后,就可以用RACCommand的相关属性看当前signal的状态
- 不同于使用信号订阅的三种状态 ==subscribeNext:== ==subscribeError:== 和 ==subscribeCompleted== 这里用RACCommand的"executionSignal"属性代表成功拿到发送的信号,"error"属性表示失败信号.
- 在执行 ==[_command execute:nil]== 前,RACCommand的executing属性默认为NO,一旦执行,executing属性会变成YES状态,直到 ==sendCompleted== 或 ==sendError== 情况时,executing属性再次变成NO.
- 由于RACCommand对信号执行状态多了更细致的描述,因此往往适用于异步的操作,如网络请求这种需要在请求状态时展示HUD、请求成功时隐藏HUD、请求成功的数据用来刷新视图的场景,统统可以写到一块一并被处理。
- 这里的代码只是模拟了网络请求,真正开发时需要封装现有的网络请求框架,在请求到数据、请求失败和请求成功时加入" ==sendNext:== "、" ==sendError:== "和" ==sendCompleted== "代码
3.RACSchedule
- RACSchedule是对GCD的一层封装,管理信号的线程任务,其原理建议参考ReactiveCocoa 中 RACScheduler是如何封装GCD的,注意其中deliverOn方法的描述.
4.RACChannel
- 主要用于RACChannel的子类RACChannelTerminal来实现UI和属性双向绑定功能,即UI变化属性随着变化,属性变化UI也跟随变化
- 代码示例:
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *textField; // 输入框
@property (nonatomic, copy) NSString *stringText; // 输入框显示的文字
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
RACChannelTerminal *textFieldChannelT = self.textField.rac_newTextChannel;
// 输入框文本变化反应到stringText属性上
RAC(self, stringText) = textFieldChannelT;
// stringText属性对应的文字订阅到输入框上 这样就实现了双向绑定
[RACObserve(self, stringText) subscribe:textFieldChannelT];
}
==扩展==
- NSUserDefaults之 ==rac_channelTerminalForKey:==
- UIDatePicker之 ==rac_newDateChannelWithNilValue:==
- UISegmentedControl之 ==rac_newSelectedSegmentIndexChannelWithNilValue:==
- UISlider之 ==rac_newValueChannelWithNilValue:==
5.RAC常用宏
- ==RAC(TARGET, [KEYPATH, [NIL_VALUE]])== 常用于给某个对象的某个属性绑定信号值
- 代码示例:
// 每当textField文本变化时,就会把文本信息传递给self的textFieldText属性
RAC(self, textFieldText) = [self.textField.rac_textSignal takeUntil:self.rac_willDeallocSignal];
- ==RACObserve(TARGET, KEYPATH)== 监听某个对象的某个属性,一旦属性值发生变化,立马激活信号。
- 代码示例:
如前面KVO的例子:
[[self rac_valuesAndChangesForKeyPath:@"testNum"
options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
NSLog(@"new value info is %@", x);
}];
使用此宏可以改写为:
[RACObserve(self, testNum) subscribeNext:^(id x) {
NSLog(@"new value is %@", x);
}];
- ==@weakify(obj)== 和 ==@strongify(obj)== 为避免Block循环引用而生的一对宏
//通常,blobk引用其外部变量,为避免持有外部对象,通常使用这一对宏来避免,如
@weakify(self);
[RACObserve(self, testNum) subscribeNext:^(id x) {
@strongify(self);
NSLog(@"new value is %@", self.x);
}];
- ==RACTurplePack== 和 ==RACTupleUnpack== 分别代表打包生成元组 和 解包分解元组。
// RACTuplePack生成元组
RACTuple *tuple = RACTuplePack(@"数据1", @2);
// RACTupleUnpack分解元组 参数:被解析的变量名
RACTupleUnpack(NSString *str, NSNumber *num) = tuple;
NSLog(@"%@ %@", str, num);
- RACChannelTo 双向绑定终端
一般情况下,使用RACChannelTo(self, name) = RACChannelTo(self.model, name);即可完成双向等值绑定功能,即一方变化另一方也跟随变化,但前提是触发属性的KVO机制才能实现。
二般情况下,举个栗子:
//首先在VC中添加两个属性:
@property (nonatomic, copy) NSString *valueA;
@property (nonatomic, copy) NSString *valueB;
// 然后添加测试方法
- (void)testRACChannelTo:(id)sender {
RACChannelTerminal *channelA = RACChannelTo(self, valueA);
RACChannelTerminal *channelB = RACChannelTo(self, valueB);
[[channelA map:^id(NSString *value) {
if ([value isEqualToString:@"西"]) {
return @"东";
}
return value;
}] subscribe:channelB];
[[channelB map:^id(NSString *value) {
if ([value isEqualToString:@"左"]) {
return @"右";
}
return value;
}] subscribe:channelA];
[[RACObserve(self, valueA) filter:^BOOL(id value) {
return value ? YES : NO;
}] subscribeNext:^(NSString* x) {
NSLog(@"你向%@", x);
}];
[[RACObserve(self, valueB) filter:^BOOL(id value) {
return value ? YES : NO;
}] subscribeNext:^(NSString* x) {
NSLog(@"他向%@", x);
}];
self.valueA = @"西";
self.valueB = @"左";
}
输出结果为:
2017-06-30 17:19:50.125 RACDemo[78483:7464849] 你向西
2017-06-30 17:19:50.126 RACDemo[78483:7464849] 他向东
2017-06-30 17:19:50.126 RACDemo[78483:7464849] 他向左
2017-06-30 17:19:50.127 RACDemo[78483:7464849] 你向右
6.信号转换
- ==map== 转换信号值,返回新的信号;
- 代码示例:
- (void)testMap {
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"石"];
return nil;
}] map:^id(NSString* value) {
if ([value isEqualToString:@"石"]) {
return @"金";
}
return value;
}];
[signal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
}
输出结果为:
2017-06-29 10:35:41.914 RACDemo[69180:6961894] 金
- ==filter== 过滤
- 代码示例:
- (void)testFilter {
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@(15)];
[subscriber sendNext:@(17)];
[subscriber sendNext:@(21)];
[subscriber sendNext:@(14)];
[subscriber sendNext:@(30)];
return nil;
}] filter:^BOOL(NSNumber* value) {
return value.integerValue >= 18;
}] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
}
//过滤大于或者等于18的数字
输出结果为:
2017-06-29 10:39:13.718 RACDemo[69242:6964994] 21
2017-06-29 10:39:13.718 RACDemo[69242:6964994] 30
- ==flattenMap== 直接转成新的信号
- 代码示例:
- (void)testFlattenMap {
[[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"打蛋液");
[subscriber sendNext:@"蛋液"];
[subscriber sendCompleted];
return nil;
}] flattenMap:^RACStream *(NSString* value) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"把%@倒进锅里面煎",value);
[subscriber sendNext:@"煎蛋"];
[subscriber sendCompleted];
return nil;
}];
}] flattenMap:^RACStream *(NSString* value) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"把%@装到盘里", value);
[subscriber sendNext:@"上菜"];
[subscriber sendCompleted];
return nil;
}];
}] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
}
输出结果为:
2017-06-29 11:02:05.035 RACDemo[69369:6975870] 打蛋液
2017-06-29 11:02:05.036 RACDemo[69369:6975870] 把蛋液倒进锅里面煎
2017-06-29 11:02:05.036 RACDemo[69369:6975870] 把煎蛋装到盘里
2017-06-29 11:02:05.036 RACDemo[69369:6975870] 上菜
- ==take== 只取前几个订阅
- 代码示例"
- (void)testTake {
RACSubject *subject = [RACSubject subject];
//只取前两次的信号值
[[subject take:2] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[subject sendNext:@"1"];
[subject sendNext:@"2"];
[subject sendNext:@"3"];
[subject sendNext:@"4"];
}
输出结果为:
2017-06-29 11:41:22.418 RACDemo[69701:7001833] 1
2017-06-29 11:41:22.418 RACDemo[69701:7001833] 2
- ==takeLast== 只取最后几个订阅
- 代码示例:
- (void)testTakeLast {
RACSubject *subject = [RACSubject subject];
//只取前两次的信号值
[[subject takeLast:2] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[subject sendNext:@"1"];
[subject sendNext:@"2"];
[subject sendNext:@"3"];
[subject sendNext:@"4"];
// 注意此处必须订阅完成 才能使用takeLast进行最后几次订阅的统计
[subject sendCompleted];
}
输出结果为:
2017-06-29 11:48:15.642 RACDemo[69834:7008684] 3
2017-06-29 11:48:15.642 RACDemo[69834:7008684] 4
- ==skip== 先跳过几个订阅
- 代码示例
// 默认UITextField创建时就能订阅到其信号,往往会跳过第一次信号
[[self.textfield.rac_textSignal skip:1] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
- ==ignore== 内部调用filter过滤,忽略掉ignore的值
- 代码示例:
- (void)testIgnore {
RACSubject *subject = [RACSubject subject];
//只取前两次的信号值
[[subject ignore:@"2"] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[subject sendNext:@"1"];
[subject sendNext:@"2"];
[subject sendNext:@"3"];
[subject sendNext:@"4"];
}
输出结果为:
2017-06-29 12:06:56.999 RACDemo[69907:7018889] 1
2017-06-29 12:06:57.000 RACDemo[69907:7018889] 3
2017-06-29 12:06:57.000 RACDemo[69907:7018889] 4
- ==distinctUntilChanged== 只订阅当前信号值不同的信号
- 代码示例
- (void)testDistinctUntilChanged {
RACSubject *subject = [RACSubject subject];
//当上一次的值和这次的值有明显变化的时候就会发出信号,否则会忽略掉
//一般用来刷新UI界面,当数据有变化的时候才会刷新
[[subject distinctUntilChanged] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[subject sendNext:@"A"];
[subject sendNext:@"A"];
[subject sendNext:@"B"];
}
输出结果为:
2017-06-29 15:39:03.417 RACDemo[70120:7083874] A
2017-06-29 15:39:03.418 RACDemo[70120:7083874] B
- ==switchTolatest== 获取信号组中的信号
- 代码示例:
如上面RACCommand的代码栗子中:
// 监控请求成功的状态
[_command.executionSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"请求成功后,拿到数据: %@", x);
}];
这里的executionSignals返回的是信号组(本身也是信号类型), RACCommand每`excute:`一次就会向信号组中扔进去新的信号,这里我们先拿到信号组,然后用switchToLatest属性拿到这个最新的信号,最后再进行订阅。
7.信号拼接
- ==concat== 拼接.注意:在signalA后拼接signalB,只有signalA发送完 ==sendCompletd== ,signalB才会拼接
- 代码示例
- (void)testConcat {
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"我恋爱啦"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"我结婚啦"];
[subscriber sendCompleted];
return nil;
}];
[[signalA concat:signalB] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
}
输出结果为:
2017-06-30 10:47:36.654 RACDemo[75454:7236811] 我恋爱啦
2017-06-30 10:47:36.655 RACDemo[75454:7236811] 我结婚啦
- ==then== 拼接.连接下一个信号,当信号完成后,才连接then后的信号
- 代码示例:
- (void)testThen {
// then:用于连接两个信号,当第一个信号完成,才会连接then返回的信号
// 注意使用then,之前信号的值会被忽略掉.
// 底层实现:1、先过滤掉之前的信号发出的值。2.使用concat连接then返回的信号
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
// 这里必须sendCompleted才能执行then后的信号
[subscriber sendCompleted];
return nil;
}] then:^RACSignal *{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@2];
return nil;
}];
}] subscribeNext:^(id x) {
// 只能接收到第二个信号的值,也就是then返回信号的值
NSLog(@"%@",x);
}];
}
输出结果为:
2017-06-30 10:44:08.578 RACDemo[75394:7233518] 2
- ==merge== 合并.任何一个信号有新值的时候就会被订阅
- 代码示例:
- (void)testMerge {
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"纸厂污水"];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"电镀厂污水"];
return nil;
}];
[[RACSignal merge:@[signalA, signalB]] subscribeNext:^(id x) {
NSLog(@"处理%@",x);
}];
}
输出结果为:
2017-06-30 10:55:58.645 RACDemo[75697:7244204] 处理纸厂污水
2017-06-30 10:55:58.645 RACDemo[75697:7244204] 处理电镀厂污水
- ==zip== (同步合并压缩)+ ==reduce== (分解元组)
- 代码示例:
- (void)testZip {
RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *zipSignal = [RACSignal zip:@[letters,numbers] reduce:^id(NSString *letter,NSString *number){
return [letter stringByAppendingString:number];
}];
[zipSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[letters sendNext:@"A"];
[letters sendNext:@"B"];
[numbers sendNext:@"1"];
[numbers sendNext:@"2"];
[numbers sendNext:@"3"];
[letters sendNext:@"C"];
[letters sendNext:@"D"];
[numbers sendNext:@"4"];
}
输出结果为:
2017-06-30 11:03:50.907 RACDemo[75837:7251093] A1
2017-06-30 11:03:50.908 RACDemo[75837:7251093] B2
2017-06-30 11:03:50.908 RACDemo[75837:7251093] C3
2017-06-30 11:03:50.908 RACDemo[75837:7251093] D4
/// zip后发出的信号值是元组类型,因此这里使用 reduce: 方法直接分解元素,拿到元组中对应的值
/// zip的合并行为是按顺序取出各个信号然后合并发出的
/// 也就是说,letters的第一个值A和number的第一个值1合并输出A1,第二个值B和number的第二个值2合并输出B2
/// 假设D后面,还有E,F,G...,但是没有对应的number信号,zip的合并行为就无法进行下去了.
- ==combineLatest== (最新值合并压缩) + ==reduce== (分解元组)
- 代码示例:
- (void)testCombineLatest{
RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *combined = [RACSignal combineLatest:@[letters,numbers] reduce:^id(NSString *letter,NSString *number){
return [letter stringByAppendingString:number];
}];
[combined subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
[letters sendNext:@"A"];
[letters sendNext:@"B"];
[numbers sendNext:@"1"];
[numbers sendNext:@"2"];
[letters sendNext:@"C"];
[numbers sendNext:@"3"];
[letters sendNext:@"D"];
[numbers sendNext:@"4"];
}
输出结果为:
2017-06-30 11:12:59.470 RACDemo[75910:7256870] B1
2017-06-30 11:12:59.471 RACDemo[75910:7256870] B2
2017-06-30 11:12:59.471 RACDemo[75910:7256870] C2
2017-06-30 11:12:59.471 RACDemo[75910:7256870] C3
2017-06-30 11:12:59.471 RACDemo[75910:7256870] D3
2017-06-30 11:12:59.472 RACDemo[75910:7256870] D4
/// combineLatest后发出的信号值是元组类型,因此这里使用 reduce: 方法直接分解元素,拿到元组中对应的值
/// 上面代码可以发现:letter信号的A值被更新的B值覆盖了,所以接下来接收到number信号的1时候,合并,输出信号B1.
/// 当接收到C的时候,与number的最新的值2合并,输出信号C2.
- ==takeUntil== 获取信号直接到某个信号开始执行
- 代码示例:
- (void)testTakeUntil {
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(id x) {
[subscriber sendNext:@"直到世界的尽头才能把我们分开"];
}];
return nil;
}] takeUntil:[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"世界的尽头到了");
[subscriber sendNext:@"世界的尽头到了"];
});
return nil;
}]] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
}
输出结果为:
2017-06-30 11:46:55.103 RACDemo[75983:7272054] 直到世界的尽头才能把我们分开
2017-06-30 11:46:56.103 RACDemo[75983:7272054] 直到世界的尽头才能把我们分开
2017-06-30 11:46:57.103 RACDemo[75983:7272054] 直到世界的尽头才能把我们分开
2017-06-30 11:46:58.103 RACDemo[75983:7272054] 直到世界的尽头才能把我们分开
2017-06-30 11:46:59.102 RACDemo[75983:7272054] 世界的尽头到了
- ==doNext== 、==doCompleted== 、==doError== 分别在 ==sendNext== ==sendCopleted== 和sendError== 前执行操作
- 代码示例:
- (void)doNextOrComleted {
[[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
[subscriber sendCompleted];
return nil;
}] doNext:^(id x) {
// 执行[subscriber sendNext:@1];之前会调用这个Block
NSLog(@"doNext");;
}] doCompleted:^{
// 执行[subscriber sendCompleted];之前会调用这个Block
NSLog(@"doCompleted");;
}] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
}
输出结果为:
2017-06-30 11:54:39.750 RACDemo[76163:7277651] doNext
2017-06-30 11:54:39.751 RACDemo[76163:7277651] 1
2017-06-30 11:54:39.751 RACDemo[76163:7277651] doCompleted
- ==rac_liftSelector== 每个信号至少sendNext一次后, 拿到每个信号最新的信号值,然后执行selector中的操作
- 代码示例:
- (void)testLiftSelector {
// 当多次数据请求时,需要全部请求结束之后才会进行下一步UI刷新等,可以用这个rac_liftSelector
// 第一部分数据
RACSignal *section01Signal01 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"section01数据请求");
[subscriber sendNext:@"section01请求到的数据"];
return nil;
}];
// 第二部分数据
RACSignal *section01Signal02 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"section02数据请求");
[subscriber sendNext:@"section02请求到的数据"];
return nil;
}];
// 第三部分数据
RACSignal *section01Signal03 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"section03数据请求");
[subscriber sendNext:@"section03请求到的数据"];
return nil;
}];
// 可以在所有信号请求完成之后再执行方法,只需把三个结果传出去即可
// 注意下面@selector中的参数数量和传递和信号数量是一致的
[self rac_liftSelector:@selector(refreshUI:::) withSignals:section01Signal01,section01Signal02,section01Signal03, nil];
}
- (void)refreshUI:(NSString *)str1 :(NSString *)str2 :(NSString *)str3 {
NSLog(@"最终数据为:\n%@\n%@\n%@", str1, str2, str3);
}
输出结果为:
2017-06-30 12:07:43.382 RACDemo[76282:7286396] section01数据请求
2017-06-30 12:07:43.382 RACDemo[76282:7286396] section02数据请求
2017-06-30 12:07:43.382 RACDemo[76282:7286396] section03数据请求
2017-06-30 12:07:43.383 RACDemo[76282:7286396] 最终数据为:
section01请求到的数据
section02请求到的数据
section03请求到的数据
8.信号时间或频率控制
- ==timeOut== 超时.超时一定时间后自动sendError
- 代码示例:
- (void)testTimeOut {
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"100"];
return nil;
}] timeout:1 onScheduler: [RACScheduler currentScheduler]]; // 这里将时间操作放在当前线程中执行
[signal subscribeNext:^(id x) {
NSLog(@"%@",x);
} error:^(NSError *error) {
NSLog(@"1秒后会自动调用");
}];
}
输出结果为:
2017-06-30 12:20:41.715 RACDemo[76418:7295742] 100
2017-06-30 12:20:42.812 RACDemo[76418:7295742] 1秒后会自动调用
- ==delay== 延迟.延迟一定时间后在进行信息传递
- 代码示例:
- (void)testDelay {
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"等等我,我还有3秒钟就到了");
[subscriber sendNext:nil];
[subscriber sendCompleted];
return nil;
}] delay:3] subscribeNext:^(id x) {
NSLog(@"我到了");
}];
}
输出结果为:
2017-06-30 12:27:15.584 RACDemo[76593:7301745] 等等我,我还有3秒钟就到了
2017-06-30 12:27:18.884 RACDemo[76593:7301745] 我到了
- ==replay== 同RACReplaySubject实现原理一样,将要传递的值打包保存一次,然后依次派送给订阅者
- 代码示例:
- (void)testReplay {
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
[subscriber sendNext:@2];
return nil;
}] replay]; // 使用replay后上面block中的代码只执行一次
[signal subscribeNext:^(id x) {
NSLog(@"第一个订阅者%@",x);
}];
[signal subscribeNext:^(id x) {
NSLog(@"第二个订阅者%@",x);
}];
}
输出结果为:
2017-06-30 14:39:10.989 RACDemo[76768:7350292] 第一个订阅者1
2017-06-30 14:39:11.778 RACDemo[76768:7350292] 第一个订阅者2
2017-06-30 14:39:12.394 RACDemo[76768:7350292] 第二个订阅者1
2017-06-30 14:39:13.068 RACDemo[76768:7350292] 第二个订阅者2
- ==retry== 错误重试,订阅者sendError后重新执行信号block操作
- 代码示例:
- (void)testRetry {
__block int failedCount = 0;
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
if (failedCount < 5) {
failedCount++;
NSLog(@"我失败了");
[subscriber sendError:nil];
}else{
NSLog(@"经历了五次失败后");
[subscriber sendNext:nil];
}
return nil;
}] retry] subscribeNext:^(id x) {
NSLog(@"终于成功了");
}];
}
输出结果为:
2017-06-30 14:47:19.473 RACDemo[76899:7358317] 我失败了
2017-06-30 14:47:19.474 RACDemo[76899:7358317] 我失败了
2017-06-30 14:47:19.475 RACDemo[76899:7358317] 我失败了
2017-06-30 14:47:19.475 RACDemo[76899:7358317] 我失败了
2017-06-30 14:47:19.475 RACDemo[76899:7358317] 我失败了
2017-06-30 14:47:19.475 RACDemo[76899:7358317] 经历了五次失败后
2017-06-30 14:47:19.475 RACDemo[76899:7358317] 终于成功了
另外:
- (RACSignal *)retry:(NSInteger)retryCount; 方法可以控制重试的次数。
- ==interval== 定时,每隔一段时间激活一次信号
- 代码示例:
- (void)testInterval {
__block int num = 0;
[[[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] takeUntil:self.rac_willDeallocSignal] subscribeNext:^(id x) {
num++;
NSLog(@"count:%d", num);
}];
}
输出结果为:
2017-06-30 14:57:02.553 RACDemo[77081:7365782] count:1
2017-06-30 14:57:03.553 RACDemo[77081:7365782] count:2
2017-06-30 14:57:04.553 RACDemo[77081:7365782] count:3
2017-06-30 14:57:05.553 RACDemo[77081:7365782] count:4
2017-06-30 14:57:06.553 RACDemo[77081:7365782] count:5
···
- ==throttle== 节流.一定时间内不接收任何信号内容,一旦到达这个时间就获取最新最近的信号内容
- 代码示例:
- (void)testThrottle {
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"旅客A"];
// 1秒时发送一条信息
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"旅客B"];
});
// 1.5秒时发送一条信息
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"插队旅客"];
});
// 2秒时发送三条信息
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"旅客C"];
[subscriber sendNext:@"旅客D"];
[subscriber sendNext:@"旅客E"];
});
// 3秒时发送一条信息
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"旅客F"];
});
return nil;
}] throttle:1] subscribeNext:^(id x) {
NSLog(@"%@通过了",x);
}];
}
输出结果为:
2017-06-30 15:10:44.948 RACDemo[77224:7375779] 旅客A通过了
2017-06-30 15:10:46.947 RACDemo[77224:7375779] 旅客E通过了
2017-06-30 15:10:48.046 RACDemo[77224:7375779] 旅客F通过了
网友评论