ReactiveCocoa 入门知识——归总篇
ReactiveCocoa (RAC) 是一个Cocoa框架,受Functional Reactive Programming启发。它提供Api合成变换(composing and transforming)随着时间改变的数据流。 介绍
ReactiveCocoa来源于functional reactive programming(Input and Output)。区别于使用不断变化修改的变量,RAC提供了“事件流”,通过 Signal 和 SignalProducer 类型来表示, 它们随着时间发送值。事件流统一了Cocoa用于事件和异步处理的常用模式,包括: 委托方法 回调blocks 通知 控件的actions和响应事件链 Futures and promises Key-value observing (KVO)
因为这些不同的机制能够用一种相同的方式处理,可以很容易的声明成链(chain)并且把它们联合在一起,用更少的代码和状态连接它们。 基本使用
1.创建信号
方法1:
RACSubscriber:表示订阅者的意思,用于发送信号,这是一个协议,不是一个类,只要遵守这个协议,并且实现方 v 法才 能成为订阅者。通过create创建的信号,都有一个订阅者,帮助他发送数据。
RACDisposable:用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它。
RACSignal *siganl = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// block调用时刻:每当有订阅者订阅信号,就会调用block。
// 2.发送信号
[subscriber sendNext:@1];
// 如果不在发送数据,发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
// block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
// 执行完Block后,当前信号就不在被订阅了。
NSLog(@"信号被销毁");
}];
}];
// 3.订阅信号,才会激活信号.
[siganl subscribeNext:^(id x) {
// block调用时刻:每当有信号发出数据,就会调用block.
NSLog(@"接收到数据:%@",x);
}];
2017-06-06 16:34:27.387 ReactiveCocoaDome[17827:6819312] 接收到数据:1
2017-06-06 16:34:27.387 ReactiveCocoaDome[17827:6819312] 信号被销毁
方法2:
// 1.创建信号
RACSubject *subject = [RACSubject subject];
// 2.订阅信号
[subject subscribeNext:^(id x) {
// block调用时刻:当信号发出新值,就会调用.
NSLog(@"第一个订阅者%@",x);
}];
[subject subscribeNext:^(id x) {
// block调用时刻:当信号发出新值,就会调用.
NSLog(@"第二个订阅者%@",x);
}];
// 3.发送信号
[subject sendNext:@"1"];
2017-06-06 16:34:27.388 ReactiveCocoaDome[17827:6819312] 第一个订阅者1
2017-06-06 16:34:27.388 ReactiveCocoaDome[17827:6819312] 第二个订阅者1
方法3:
// RACReplaySubject使用步骤:
// 1.创建信号 [RACSubject subject],跟RACSiganl不一样,创建信号时没有block。
// 2.可以先订阅信号,也可以先发送信号。
// 2.1 订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
// 2.2 发送信号 sendNext:(id)value
// RACReplaySubject:底层实现和RACSubject不一样。
// 1.调用sendNext发送信号,把值保存起来,然后遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock。
// 2.调用subscribeNext订阅信号,遍历保存的所有值,一个一个调用订阅者的nextBlock
// 如果想当一个信号被订阅,就重复播放之前所有值,需要先发送信号,在订阅信号。
// 也就是先保存值,在订阅值。
// 1.创建信号
RACReplaySubject *replaySubject = [RACReplaySubject subject];
// 2.发送信号
[replaySubject sendNext:@1];
[replaySubject sendNext:@2];
[replaySubject sendCompleted];
// 3.订阅信号
[replaySubject subscribeNext:^(id x) {
NSLog(@"第一个订阅者接收到的数据%@",x);
}];
// 订阅信号
[replaySubject subscribeNext:^(id x) {
NSLog(@"第二个订阅者接收到的数据%@",x);
}];
RACDisposable * dispose = [RACDisposable disposableWithBlock:^{
NSLog(@"replaySubject 销毁了");
}];
RACCompoundDisposable * compoundDispose = [RACCompoundDisposable compoundDisposableWithDisposables:@[dispose]];
[replaySubject didSubscribeWithDisposable:compoundDispose];
2017-06-06 16:34:27.550 ReactiveCocoaDome[17827:6819312] 第一个订阅者接收到的数据1
2017-06-06 16:34:27.550 ReactiveCocoaDome[17827:6819312] 第一个订阅者接收到的数据2
2017-06-06 16:34:27.551 ReactiveCocoaDome[17827:6819312] 第二个订阅者接收到的数据1
2017-06-06 16:34:27.551 ReactiveCocoaDome[17827:6819312] 第二个订阅者接收到的数据2
2017-06-06 16:34:27.551 ReactiveCocoaDome[17827:6819312] replaySubject 销毁了
当然 RACReplaySubject *replaySubject = [RACReplaySubject replaySubjectWithCapacity:1]; 通过这种方式创建的 则指定了默认的储存值是1个 打印的 则为
2017-06-06 16:51:00.570 ReactiveCocoaDome[18117:6914095] 第一个订阅者接收到的数据2
2017-06-06 16:51:00.570 ReactiveCocoaDome[18117:6914095] 第二个订阅者接收到的数据2
2.针对不同对象的基本使用方法
- UIButton 添加事件 [[_pushbut rac_signalForControlEvents:UIControlEventTouchUpInside]subscribeNext:^(id x) { @strongify(self); SendViewController *sendV = [[UIStoryboard storyboardWithName:@"Main" bundle:nil]instantiateViewControllerWithIdentifier:@"SendViewController"]; sendV.delegateSingal = [RACSubject subject]; [sendV.delegateSingal subscribeNext:^(id x) { NSLog(@"点击了通知按钮 %@",x); }]; [self.navigationController pushViewController:sendV animated:YES]; }]; 触发信号 _pushbut.rac_command = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"发送数据"]; // 注意:数据传递完,最好调用sendCompleted,这时命令才执行完毕。 [subscriber sendCompleted]; return [RACDisposable disposableWithBlock:^{ }]; }]; }];
- NSArray 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); }];
- NSDictionary 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(@"元组对象 x == %@", x); NSLog(@"字典转化为 %@ %@",key,value); }];
*字典转模型 RAC高级写法 *
// map:映射的意思,目的:把原始值value映射成一个新值
// array: 把集合转换成数组
// 底层实现:当信号被订阅,会遍历集合中的原始值,映射成新值,并且保存到新的数组里。
NSArray *MMs = [[dictArr.rac_sequence map:^id(id value) {
return [MM MMwithDic:value];
}] array];
- NSNotificationCenter [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) { NSLog(@"键盘弹出"); }];
- UITextField [textF.rac_textSignal subscribeNext:^(id x) { NSLog(@"文字改变了%@",x); }];
- 定时器 //创建定时器信号,定时8个小时 RACSignal *signal = [RACSignal interval:60*60*8 onScheduler:[RACScheduler mainThreadScheduler]]; //定时执行代码 [signal subscribeNext:^(id x) { NSLog(@"吃药"); }]; RACSignal *singal = [[[[[RACSignal interval:1 onScheduler [RACSchedulerscheduler]]take:10]startWith:@(1)]map:^id(id value) { NSLog(@"%@",value); return @"发送出去的信号"; }]takeUntil:self.rac_willDeallocSignal]; [singal subscribeNext:^(id x) { NSLog(@"x == %@",x); }];
- 代理 @protocol ProgrammerDelegate - (void)makeAnApp; @end //为self添加一个信号,表示代理ProgrammerDelegate的makeAnApp方法信号 RACSignal *programmerSignal = [self rac_signalForSelector:@selector(makeAnApp) fromProtocol:@protocol(ProgrammerDelegate)]; //设置代理方法makeAnApp的实现 [programmerSignal subscribeNext:^(RACTuple* x) { //这里可以理解为makeAnApp的方法要的执行代码 NSLog(@"花了一个月,app写好了"); }]; //调用代理方法 [self makeAnApp];
3.其他常用
- 观察值的变化 (监控属性等 MVVM 中比较常用) @weakify(self); [RACObserve(self, value)subscribeNext:^(id x) { @strongify(self); NSLog(@"value 发生了变化%@",self.value); }];
- 改变信号中的值 RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"跳舞"]; [subscriber sendCompleted]; return [RACScopedDisposable disposableWithBlock:^{ }]; }]; //对信号进行改进 RAC(self,value) = [signalA map:^id(id value) { if ([value isEqualToString:@"跳舞"]) { return @"唱歌"; } return @""; }];
- 广播 通知 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; //注册广播通知 RACSignal *signal = [center rac_addObserverForName:@"代码之道频道" object:nil]; //设置接收到通知的回调处理 [signal subscribeNext:^(NSNotification* x) { NSLog(@"技巧:%@", x.userInfo[@"技巧"]); }]; //发送广播通知 [center postNotificationName:@"代码之道频道" object:nil userInfo:@{@"技巧":@"用心写"}];
- 串联 并联 //创建一个信号管A { RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) { //发送一个Next玻璃球和一个Complete玻璃球 [subscriber sendNext:@"我恋爱啦"]; [subscriber sendCompleted]; return nil; }]; //创建一个信号管B RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) { //发送一个Next玻璃球和一个Complete玻璃球 [subscriber sendNext:@"我结婚啦"]; [subscriber sendCompleted]; return nil; }]; //串联管A和管B RACSignal *concatSignal = [signalA concat:signalB]; //串联后的接收端处理 [concatSignal subscribeNext:^(id x) { NSLog(@"%@",x); }]; //打印:我恋爱啦 我结婚啦 } { //创建信号A RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) { [subscriber sendNext:@"纸厂污水"]; return nil; }]; //创建信号B RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) { [subscriber sendNext:@"电镀厂污水"]; return nil; }]; //并联2个信号 RACSignal *mergeSignal = [RACSignal merge:@[signalA, signalB]]; [mergeSignal subscribeNext:^(id x) { NSLog(@"处理%@",x); }]; }
- 组合 { //定义2个自定义信号 RACSubject *letters = [RACSubject subject]; RACSubject *numbers = [RACSubject subject]; //组合信号 [[RACSignal combineLatest:@[letters, numbers] reduce:^(NSString *letter, NSString *number){ //把2个信号的信号值进行字符串拼接 return [letter stringByAppendingString:number]; }] subscribeNext:^(NSString * x) { NSLog(@"%@", x); }]; //自己控制发送信号值 [letters sendNext:@"A"]; [letters sendNext:@"B"]; [numbers sendNext:@"1"];//打印B1 [letters sendNext:@"C"];//打印C1 [numbers sendNext:@"2"];//打印C2 }
- 映射 //创建信号,发送"石"玻璃球 RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) { [subscriber sendNext:@"石"]; return nil; }]; //对信号进行改造,改造"石"为"金" signal = [signal map:^id(NSString *value) { if ([value isEqualToString:@"石"]) { return @"金"; } return value; }]; 打印 [signal subscribeNext:^(id x) { NSLog(@"%@", x);//金 }];
- 过滤 //创建信号 RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) { [subscriber sendNext:@(15)]; [subscriber sendNext:@(17)]; [subscriber sendNext:@(21)]; [subscriber sendNext:@(14)]; [subscriber sendNext:@(30)]; return nil; }]; //过滤信号,并打印 [[signal filter:^BOOL(NSNumber* value) { //值大于等于18的才能通过过滤网 return value.integerValue >= 18; }] subscribeNext:^(id x) { NSLog(@"%@", x); }]; //打印:21 30
- 命令 //创建命令 RACCommand *aCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(NSString *input) { //命令执行代码 NSLog(@"%@我投降了",input); //返回一个RACSignal信号 return [RACSignal createSignal:^RACDisposable *(id subscriber) { [subscriber sendCompleted]; return nil; }]; }]; //执行命令 [aCommand execute:@"今天"]; //打印:今天我投降了
- 延迟 //创建一个信号 RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) { NSLog(@"等等我,我还有10秒钟就到了"); [subscriber sendNext:@"天河中心"]; [subscriber sendCompleted]; return nil; }]; //延时10秒接受Next玻璃球 [[signal delay:10] subscribeNext:^(NSString x) { NSLog(@"我到了%@",x); }]; / [2016-04-21 13:20:10]等等我,我还有10秒钟就到了 [2016-04-21 13:20:20]我到了天河中心 */
- 超时处理 RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) { //创建发送信息信号 NSLog(@"我快到了"); RACSignal sendSignal = [RACSignal createSignal:^RACDisposable (id sendSubscriber) { [sendSubscriber sendNext:nil]; [sendSubscriber sendCompleted]; return nil; }]; //发送信息要1个小时10分钟才到 [[sendSignal delay:6070] subscribeNext:^(id x) { //这里才发送Next玻璃球到signal [subscriber sendNext:@"我到了"]; [subscriber sendCompleted]; }]; return nil; }]; //这里对signal进行超时接受处理 [[signal timeout:6060 onScheduler:[RACScheduler mainThreadScheduler] ] subscribeError:^(NSError *error) { //超时错误处理 NSLog(@"等了你一个小时了,你还没来,我走了"); }];
- 重试 __block int failedCount = 0; //创建信号 RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) { if (failedCount < 100) { failedCount++; NSLog(@"我失败了"); //发送错误,才会要重试 [subscriber sendError:nil]; } else { NSLog(@"经历了数百次失败后"); [subscriber sendNext:nil]; } return nil; }]; //重试 RACSignal *retrySignal = [signal retry]; //直到发送了Next玻璃球 [retrySignal subscribeNext:^(id x) { NSLog(@"终于成功了"); }];
- 节流 RACSignal *signal = [RACSignal createSignal:^RACDisposable (id subscriber) { //即时发送一个Next玻璃球 [subscriber sendNext:@"A"]; //下面是GCD延时发送Next玻璃球 dispatch_queue_t mainQueue = dispatch_get_main_queue(); dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(1 * NSEC_PER_SEC)),mainQueue, ^{ [subscriber sendNext:@"B"]; }); dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(2 * NSEC_PER_SEC)),mainQueue, ^{ //发送多个Next,如果节流了,接收最新发送的 [subscriber sendNext:@"C"]; [subscriber sendNext:@"D"]; [subscriber sendNext:@"E"]; }); dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(3 * NSEC_PER_SEC)),mainQueue, ^{ [subscriber sendNext:@"F"]; }); return nil; }]; //对信号进行节流,限制短时间内一次只能接收一个Next玻璃球 [[signal throttle:1] subscribeNext:^(id x) { NSLog(@"%@通过了",x); }]; / A B E F *
- 条件 (直到什么时候才 takeUntil) //创建取值信号 RACSignal *takeSignal = [RACSignal createSignal:^RACDisposable *(id subscriber) { //创建一个定时器信号,每隔1秒触发一次 RACSignal *signal = [RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]]; //定时接收 [signal subscribeNext:^(id x) { [subscriber sendNext:@"我不玩游戏除非你求我"]; }]; return nil; }]; //创建条件信号 RACSignal *conditionSignal = [RACSignal createSignal:^RACDisposable *(id subscriber) { //设置5秒后发生Complete玻璃球 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"我求你了"); [subscriber sendCompleted]; }); return nil; }]; //设置条件,takeSignal信号在conditionSignal信号接收完成前,不断地取值 [[takeSignal takeUntil:conditionSignal] subscribeNext:^(id x) { NSLog(@"%@", x); }]; /* 我不玩游戏除非你求我 我不玩游戏除非你求我 我不玩游戏除非你求我 我不玩游戏除非你求我 我不玩游戏除非你求我 我求你了 */
4.常用宏
// RAC 用于给某个对象的属性绑定
RAC(self.sendBut.titleLabel,text) = self.textF.rac_textSignal;
//RACObserve(self, name):监听某个对象的某个属性,返回的是信号。
[RACObserve(self.view, center)subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
@weakify(self); @strongify(self);
// RACTuplePack:把数据包装成RACTuple(元组类)
// 把参数中的数据包装成元组
RACTuple *tuple1 = RACTuplePack(@10,@20);
//RACTupleUnpack:把RACTuple(元组类)解包成对应的数据。
// 把参数中的数据包装成元组
RACTuple *tuple = RACTuplePack(@"img",@20);
// 解包元组,会把元组的值,按顺序给参数里面的变量赋值
// name = @"xmg" age = @20
RACTupleUnpack(NSString *name,NSNumber *age) = tuple;
5.项目中使用示例
网络请求 - (RACSubject *)POST:(NSString *)path parameters:(id)parameters { RACReplaySubject *signal = [RACReplaySubject replaySubjectWithCapacity:1]; NSURLSessionDataTask *task = [[PR_HTTPSessionManager defaultManager] POST:path parameters:[[self class] addPublicParameters:parameters] success:^(NSURLSessionDataTask *task, id responseObject){ NSDictionary * responseDict = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:nil];
NSError *error = [[self class] errorForResponseObject:responseDict];
if (error) {
[signal sendError:error];
} else {
id data = responseDict[@"results"];
[signal sendNext:data];
[signal sendCompleted];
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
[signal sendError:[[self class] localizedError:error]];
}];
@weakify(task);
RACDisposable * dispose = [RACDisposable disposableWithBlock:^{
@strongify(task);
[task cancel];
}];
RACCompoundDisposable * compoundDispose = [RACCompoundDisposable compoundDisposableWithDisposables:@[dispose]];
[signal didSubscribeWithDisposable:compoundDispose];
return signal;
}
+ (RACSignal *)updateUserInfo:(NSDictionary *)params {
RACSignal *signal = [RACSignal empty];
NSString *method = @"xxxxxx";
signal =
[[PR_HTTPSessionManager defaultManager] POST:method parameters:params];
return signal;
}
一段赋值代码
@weakify(self);
RAC(self.suggestTitleLabel,text) = RACObserve(self, viewModel.title);
[[RACObserve(self, viewModel) ignore:nil] subscribeNext:^(DoctorSuggestTableViewCellModel *cellViewModel) {
@strongify(self);
[self.textField setText:cellViewModel.contentString];
self.suggestCollectionView.allowsMultipleSelection = cellViewModel.allowsMultipleSelection;
[[self.textField.rac_textSignal takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(id x) {
@strongify(self);
self.viewModel.contentString = x;
}];
[[RACObserve(self.otherTextView,content) takeUntil:self.rac_prepareForReuseSignal]subscribeNext:^(id x) {
@strongify(self);
self.viewModel.descriptionString = x;
}];
}];
- js中数组(Array)的排序(sort)注意事项
- 双机热备工作模式及高内聚低耦合架构解释
- linux下拷贝命令中的文件过滤操作记录
- 关于智慧城市的十大反思(上)
- scrollTop与offsetTop研究
- JQuery笔记(四) 通用选择的尝试
- Docker容器学习梳理--基础环境安装
- Javascript:模仿淘宝的信用评价
- 好米有好价! 两枚4字母域名均以五位数交易
- Docker容器学习梳理--Volume数据卷使用
- 菜单常用:复位全部并设置某个项的样式
- Mysql更换MyISAM存储引擎为Innodb的操作记录
- 比特币分叉倒计时,糖果福利又来了
- 执行git push出现"Everything up-to-date"
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Nginx应用场景之虚拟主机
- shell tcping 端口,ping网段所有ip端口或tcping指定IP端口
- Redis | 源码阅读 —— 链表
- Python从入门到熟练(4):基础数据类型
- 【MaskTheFace】给人脸图片戴口罩!
- Python从入门到熟练(5): 数据类型进阶
- Python从入门到熟练(6): 流程控制
- Python包:json扩展包demjson使用
- 【论文解读】无需额外数据、Tricks、架构调整,CMU开源首个将ResNet50精度提升至80%+新方法
- 机器学习模型调参指南(附代码)
- Flink SQL 自定义函数指南 - 以读取 GBK 编码的数据库为例
- 光照不均匀图像分割技巧1——分块阈值
- MySQL 8.0新特性 — 不可见索引
- 【小白学PyTorch】9.tensor数据结构与存储结构
- 【Python相关】jupyter平台最强插件没有之一