ReactiveCocoa代码实践之-更多思考
三.ReactiveCocoa代码实践之-更多思考
1. RACObserve()宏形参写法的区别
之前写代码考虑过 RACObserve(self.timeLabel , text) 和 RACObserve(self , timeLabel.text) 的区别。 因为这两种方法都是观察self.timeLabel.text的属性,并且都能实现功能。估计是作者原本用的其中一种后来对另一种也提供了支持,究竟有什么区别哪一种写法更好?
点进去看RACObserve的源码 大多都是方法调用,一层一层点进去最后来到这个方法。
- - (RACDisposable *)rac_observeKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)weakObserver block:(void (^)(id, NSDictionary *, BOOL, BOOL))block
这个方法里面把逗号后面的keypath通过“.” 进行分割成了一个数组。 并且得到三个属性
- BOOL keyPathHasOneComponent = (keyPathComponents.count == 1);
- NSString *keyPathHead = keyPathComponents[0];
- NSString *keyPathTail = keyPath.rac_keyPathByDeletingFirstKeyPathComponent;
这里会取到keypatch的头和去掉头的部分,并且会在下面方法内部自己调用自己
- // Adds the callback block to the remaining path components on the value. Also
- // adds the logic to clean up the callbacks to the firstComponentDisposable.
- void (^addObserverToValue)(NSObject *) = ^(NSObject *value) {
- RACDisposable *observerDisposable = [value rac_observeKeyPath:keyPathTail options:(options & ~NSKeyValueObservingOptionInitial) observer:weakObserver block:block];
- [firstComponentDisposable() addDisposable:observerDisposable];
- };
并且把keyPathTail 作为keypatch传进去了,就是递归调用,每一次进来都会切掉第一个元素,直到BOOL keyPathHasOneComponent 这个值等于yes。从这个角度看用RACObserve(self , timeLabel.text) 这种写法会引发递归调用,性能不如RACObserve(self.timeLabel.text)。
更多RAC宏相关知识可见这篇 :http://blog.sunnyxx.com/2014/03/06/rac_1_macros/
2.集合操作
假设现在有一个需求,有一串密码的数组,我们判断密码长度小于6位就是太短,就会系统内部抛出一个消息:XXX密码太短不合格。采用RAC的写法会比常规写法方便,一个过滤一个自定义然后直接返回。
- NSArray *pwds = @[@"567887",@"89877",@"789",@"7899000"];
- RACSequence *results = [[pwds.rac_sequence
- filter:^ BOOL (NSString *pwd) {
- return pwd.length < 6;
- }]map:^id(NSString *pwd) {
- return [[pwd mutableCopy]stringByAppendingString:@"密码太短不合格"];
- }];
- NSLog(@"%@",results.array);
中间filter方法的block内代码会在下面results.array代码执行时才会执行, 相当于是有了个订阅者才会执行。这一点和RACSignal很像,因为signal 和 sequence 都是streams,他们共享很多相同的方法signal是push驱动的stream,sequence是pull驱动的stream。
如果相从RACSequence对象中取出其他属性时进行操作也可以用如下方法
- RACSequence *s = [RACSequence sequenceWithHeadBlock:^id{
- return @"自定义操作";
- } tailBlock:^RACSequence *{
- return [RACSequence new];
- }];
- NSLog(@"%@",s.head);
- NSLog(@"%@",s.tail);
两个block分别会在指定属性被调用时才会执行,注意head就是sequence的第一个元素,而tail是除去第一个元素的剩余所有,所以还是一个sequence。(董铂然博客园)
3.信号实现游戏技能释放
假设现在需要用RAC模拟一个街机里放爆气技能的方法。 按下了指定的按钮顺序下前下前拳就会释放绝招。
首先需要将各个按钮连线,并设置一个信号来监听所有按键单独信号的并集,捕捉到每个按钮的title。
- // 把六个按键的信号合并
- RACSignal *comboSignal = [[RACSignal merge:@[
- [self.topBtn rac_signalForControlEvents:UIControlEventTouchUpInside],
- [self.bottomBtn rac_signalForControlEvents:UIControlEventTouchUpInside],
- [self.leftBtn rac_signalForControlEvents:UIControlEventTouchUpInside],
- [self.rightBtn rac_signalForControlEvents:UIControlEventTouchUpInside],
- [self.BBtn rac_signalForControlEvents:UIControlEventTouchUpInside],
- [self.ABtn rac_signalForControlEvents:UIControlEventTouchUpInside]]]
- map:^id(UIButton *btn) {
- return btn.currentTitle;
- }];
然后对这个信号源进行buffer操作,把每三秒收到的所有按键信息都捕获到,并进行判断和后继操作
- // 设置触发爆气条件
- NSString *comboCode = @"下前下前拳";
- // 实际操作
- RACSignal *canAction = [[[comboSignal bufferWithTime:3 onScheduler:[RACScheduler mainThreadScheduler]] map:^id(RACTuple *value) {
- return [[value allObjects] componentsJoinedByString:@""];
- }] map:^id(NSString *value) {
- return @([value containsString:comboCode]);
- }];
- // 调用combo:方法就是技能释放
- [self rac_liftSelector:@selector(combo:) withSignalsFromArray:@[canAction]];
上面的代码可以实现预计的功能,只要你能在三秒的buffer内按出指定的按键就能释放。但是用这个方法中间也有一个问题:设置了buffer3秒后这个block里面每隔三秒才会来到一次,也就是说如果你在0.5秒内就按出了技能,那也需要再等2.5秒才能放出技能,显然这个在实战中是不能接受的。
于是尝试了其他的实现思路,尝试了takeLast:及takeUntilBlock:及scanWithStart: 等方法都不是很合适,最后使用了aggregateWithStart: 达到了需求的目的。
- // 设置触发爆气条件
- NSString *comboCode = @"下前下前拳";
- // 实际操作
- _time = [[[NSDate alloc] init] timeIntervalSince1970];
- [[comboSignal aggregateWithStart:@"" reduce:^id(NSString* running, NSString* next) {
- if (([[[NSDate alloc] init] timeIntervalSince1970] - _time) < 3){
- NSString *str = [NSString stringWithFormat:@"%@%@",running,next];
- return [str containsString:comboCode]?[self combo]:str;
- }
- _time = [[[NSDate alloc] init] timeIntervalSince1970];
- return str.length < combo.length ? str : [str subStringFromIndex:str.length - comboCode.length];
- }]subscribeNext:^(id x){
- }];
使用这段代码可以在满足之前条件的前提下,并且按钮一按完马上触发技能。
aggregateWithStart:reduce:的第一个参数是初始值,第二个参数是一个block,这个block的返回值就是下一次来到这个block的 running参数。我在这个block的循环中做的操作有:
1.对时间进行delta计算,如果距离上一次时间节点大于3秒,刷新时间节点重新计时。 str小于5则返回,大于5则截取后五位返回。
2.如果小于3秒则把每次按键信息聚合成一个字符串并判断是否包含技能触发代码。
3.满足的话触发技能,技能方法的内部也刷新了时间节点,并截取running(保留最后4位,防止上一个循环结束和下一个循环开始所满足的条件)。不满足则将这个字符串继续返回。
虽然代码写的不是很好看,但是功能是实现了,感觉有点别扭,因为函数式编程倡导的是引用透明无副作用,所以上面需要记录值和成员变量的做法很明显就不适合用RAC了,应该还会有更好的方法实现。
4.其他RAC操作
1)映射:flattenMap,Map用于把源信号内容映射成新的内容
2)组合:concat:按一定顺序拼接信号,当多个信号发出的时候,有顺序的接收信号
3)`then`:用于连接两个信号,当第一个信号完成,才会连接then返回的信号
4)`merge`:把多个信号合并为一个信号,任何一个信号有新值的时候就会调用
5)`combineLatest`:将多个信号合并起来,并且拿到各个信号的最新的值,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号。
6)`reduce`聚合:用于信号发出的内容是元组,把信号发出元组的值聚合成一个值
7)filter:过滤信号,使用它可以获取满足条件的信号.
8) ignore:忽略完某些值的信号.
9) distinctUntilChanged:当上一次的值和当前的值有明显的变化就会发出信号,否则会被忽略掉
10) take:从开始一共取N次的信号
11)takeLast:取最后N次的信号,前提条件,订阅者必须调用完成,因为只有完成,就知道总共有多少信号
12)takeUntil:(RACSignal *):获取信号直到某个信号执行完成
13)skip:(NSUInteger):跳过几个信号,不接受
14)switchToLatest:用于signalOfSignals(信号的信号),有时候信号也会发出信号,会在signalOfSignals中,获取signalOfSignals发送的最新信号
15)doNext: 执行Next之前,会先执行这个Block
16)doCompleted: 执行sendCompleted之前,会先执行这个Block
17)deliverOn: 内容传递切换到制定线程中,副作用在原来线程中,把在创建信号时block中的代码称之为副作用
18)subscribeOn: 内容传递和副作用都会切换到制定线程中
19)interval 定时:每隔一段时间发出信号
20)delay 延迟发送next。
21) 代替代理:
- rac_signalForSelector:用于替代代理。
22) 代替KVO :
- rac_valuesAndChangesForKeyPath:用于监听某个对象的属性改变。
23) 监听事件:
- rac_signalForControlEvents:用于监听某个事件。
24) 代替通知:
- rac_addObserverForName:用于监听某个通知。
25) 监听文本框文字改变:
- rac_textSignal:只要文本框发出改变就会发出这个信号。
26) 处理当界面有多次请求时,需要都获取到数据时,才能展示界面
- rac_liftSelector:withSignalsFromArray:Signals:当传入的Signals(信号数组),每一个signal都至少sendNext过一次,就会去触发第一个selector参数的方法。
- 使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据
RAC曾经被冠以 学习成本搞,可读性差,debug的噩梦等不良评价,但随着近几年的演变已逐渐被企业级项目所接受,并且成为函数响应式编程主流框架。RAC用人越来越多,随笔和博客也越来越多,学习的门槛已经大大降低。 并且我觉得初学者没有必要一开始就把所有操作和概念都弄懂,可以从简单的用法开始一步步的接触高阶语法,这样会更容易接受。
ReactiveCocoa代码实践之-更多思考的更多相关文章
- ReactiveCocoa代码实践之-RAC网络请求重构
前言 RAC相比以往的开发模式主要有以下优点:提供了统一的消息传递机制:提供了多种奇妙且高效的信号操作方法:配合MVVM设计模式和RAC宏绑定减少多端依赖. RAC的理论知识非常深厚,包含有FRP,高 ...
- ReactiveCocoa代码实践之-UI组件的RAC信号操作
上一节是自己对网络层的一些重构,本节是自己一些代码小实践做出的一些demo程序,基本涵盖大多数UI控件操作. 一.用UISlider实现调色板 假设我们现在做一个demo,上面有一个View用来展示颜 ...
- 机器学习十大算法总览(含Python3.X和R语言代码)
引言 一监督学习 二无监督学习 三强化学习 四通用机器学习算法列表 线性回归Linear Regression 逻辑回归Logistic Regression 决策树Decision Tree 支持向 ...
- Java的BIO和NIO很难懂?用代码实践给你看,再不懂我转行!
本文原题“从实践角度重新理解BIO和NIO”,原文由Object分享,为了更好的内容表现力,收录时有改动. 1.引言 这段时间自己在看一些Java中BIO和NIO之类的东西,也看了很多博客,发现各种关 ...
- TextCNN代码实践
在上文<TextCNN论文解读>中已经介绍了TextCNN的原理,本文通过tf2.0来做代码实践. 数据集:来自中文任务基准测评的数据集IFLYTEK 导库 import os impor ...
- 深刻理解Python中的元类(metaclass)--代码实践
根据http://blog.jobbole.com/21351/所作的代码实践. 这篇讲得不错,但以我现在的水平,用到的机会是很少的啦... #coding=utf-8 class ObjectCre ...
- word2vector代码实践
引子 在上次的 <word2vector论文笔记>中大致介绍了两种词向量训练方法的原理及优劣,这篇咱们以skip-gram算法为例来代码实践一把. 当前教程参考:A Word2Vec Ke ...
- 机器学习(四):通俗理解支持向量机SVM及代码实践
上一篇文章我们介绍了使用逻辑回归来处理分类问题,本文我们讲一个更强大的分类模型.本文依旧侧重代码实践,你会发现我们解决问题的手段越来越丰富,问题处理起来越来越简单. 支持向量机(Support Vec ...
- R语言︱情感分析—词典型代码实践(最基础)(一)
每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- 笔者寄语:词典型情感分析对词典要求极高,词典中 ...
随机推荐
- C指针(二)
原文链接:http://www.orlion.ga/924/ 一.指针与const限定符 const限定符与指针结合起来常见的情况有一下几种: const int *a; int const *a; ...
- .cn根服务器被攻击之后
如果是互联网行业的人员应该知道,8月25日凌晨,大批的“.cn”域名的网站都无法访问,当然包括weibo.cn等大型网站.个人比较奇怪的一件事情是,微博PC网页版是:www.weibo.com,而mo ...
- php变量-单引号不编译,双引号编译
<?php header("Content-type:text/html;charset='utf8'"); error_reporting(E_ALL); $sTemp = ...
- ASP.NET系统国际化总结
引言 系统要求同时支持中英文,以前对国际化这块只是听说过,从来没有自己动手过,提到国际化那么首先肯定想到的就是资源文件,也确实是这样,于是乎我从开始着手系统国际化功能时前前后后共投入了4次时间段,每次 ...
- js构建ui的统一异常处理方案(一)
从早期从事基于java的服务器端开发,再到之后从事基于web和js的ui开发,总体感觉基于web页面的ui开发远不如服务器端健壮.主要是早期ie浏览器功能太弱小,很多业务被迫放到服务器端去实现,浏览器 ...
- jQuery-1.9.1源码分析系列(十六)ajax——ajax处理流程以及核心函数
先来看一看jQuery的ajax核心处理流程($.ajax) a. ajax( [url,] options )执行流程 第一步,为传递的参数做适配.url可以包含在options中 //传递的参数只 ...
- 初来乍到 Java 和 .Net 迭代器功能
最近有一个需求是这样的, 根据键值对存储类型数据,也算是数据缓存块模块功能设计. 一个键对应多个值.每一个键的值类型相同,但是每个不同的键之间类型不一定相同. Java 设计如下 HashMap< ...
- centos6搭建gitlab
前言 原来的项目放在公网的gitlab上,处于安全考虑,在内网搭建一套,有图形界面,可以直接从外网git导入进来,使用了一下觉得挺方便,把安装流程记录下来,参考官网:https://gitlab.co ...
- .NET 扩展方法 (二)
上一篇随笔 .NET 扩展方法 (一) 已经对 扩展方法有了大致的介绍,这篇算是一个补充,让我们来看一下扩展方法的几个细节: 一.扩展方法具有继承性 当使用扩展方法扩展一个类型的时候,其也扩展了派生类 ...
- HTML5小游戏之见缝插针
今天给大家带来的就是一款叫做<见缝插针>的游戏.有空你就往里插,直到你无处可插!看你能过多少关! 简洁大气 黑白搭配游戏画面非常的简洁,米白色的背景中央,放置着一个不断旋转的太阳状的球体, ...