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代码实践之-更多思考

时间: 2024-10-21 11:36:32

ReactiveCocoa代码实践之-更多思考的相关文章

关于网页脚本代码结构的再思考

在很多说法中,总是建议将我们的javascript脚本加载在网页的最后,并用外部文件的形式,然而事实并不是这样,外挂的文件最好不要太多,脚本结构代码本身才是值得我们思考的问题.我们需要重新思考我们撰写的脚本的执行力,并把更优秀的javascript开发思路融入到我们的开发中. 我在读完了几篇关于javascript和jQuery的性能优化的文章之后,才恍然大悟,我以前所做的很多代码结构优化,最终只是让乌徒帮显得臃肿,于是重新设计脚本代码的结构,无论怎么样,乌徒帮现在的网页打开显得更加流畅了. 1

关于代码质量的一些思考

关于代码质量的一些思考 今天刚好看到同事写一段代码,跟同事聊到一个代码风格的问题,讨论了一会,也没得出什么结果.回来想了想,之所以大家观点不一样,其实是一开始代码追求的目的就不一样. 1. 可读性 我是一直认为代码的可读性是最重要的目标.太多的书都讲到一个观点:"代码是写给人阅读的,只不过刚好能被计算机执行". 大部分做自己产品的团队,一个项目的维护时间可能是开发时间的5倍以上,而维护的常见内容都是一些小功能以及已有bug的修复.可读性带来的好处就是,非常容易弄清一段功能逻辑,从而定位

深刻理解Python中的元类(metaclass)--代码实践

根据http://blog.jobbole.com/21351/所作的代码实践. 这篇讲得不错,但以我现在的水平,用到的机会是很少的啦... #coding=utf-8 class ObjectCreator(object): pass my_object = ObjectCreator() # print my_object def echo(o): print o echo(ObjectCreator) print hasattr(ObjectCreator, 'new_attribute'

R语言代码规范

1.一般性规则 避免使用attach写函数是尽量少的使用stop()定义S3和S4的对象不要混在一起使用2.文件命名 以.r结束的文件,尽可能的增加信息在文件名里面,比如 Good: predict_ad_revenue.R Bad: foo.R 3.变量名和函数命名规则 # 注意,在R环境下,大小写是敏感的 变量: Good: avg.clicks Bad: avg_Clicks, avgClicks 函数名: Good: CalculateAvgClicks Bad: calculate_a

高质量代码实践

本博文首先分析了强调高质量代码的原因.判别标准::由于需求的不断变化,系统功能越来越多,而且越来越复杂,软件规模越来越大(代码行数>100万),导致开发以及维护的成本越来越高,开发效率越来越低,Bug越来越多,因此好的产品需要高质量的代码构建,从而提高开发效率,提升产品稳定性,输出外在质量高.内在质量高的产品: 本博文然后从代码实践中总结归纳出怎样写出高质量的代码::从基础的命名(名副其实,做有意义的区分,使用解决方案/业务领域可读的名称,类名/方法名,命名参考)到函数.类以及常用的设计模式.面

R语言︱情感分析—词典型代码实践(最基础)(一)

笔者寄语:词典型情感分析对词典要求极高,词典中的词语需要人工去选择,但是这样的选择会很有目标以及针对性.本文代码大多来源于<数据挖掘之道>的情感分析章节.本书中还提到了监督算法式的情感分析,可见博客: R语言︱情感分析-基于监督算法R语言实现笔记. 可以与博客 R语言︱词典型情感分析文本操作技巧汇总(打标签.词典与数据匹配等)对着看. 词典型情感分析大致有以下几个步骤: 训练数据集.neg/pos情感词典.分词+数据清洗清洗(一.二.三级清洗步骤).计算情感得分.模型评价 ----------

关于使用第三方库、代码复用的一些思考

不管是不要重复造轮子,还是站在巨人的肩膀上,对于软件开发来说,代码复用都是最基本的原则之一. 代码复用,可能是DIY(dont repeat yourself),也可能是使用别人的代码,或者是开源项目,或者是其他团队提供的组件.服务,或者是团队内他人实现的公共模块,这些复用大大减少了项目的开发周期和成本. 但怎样才算是高效.正确的第三方代码使用姿势呢?在实操中,也会出现一些使用第三方代码导致失控的情况,比如使用用了一些第三方代码,但年久失修,当线上事故貌似与第三方代码有关时,无法快速定位.解决问

初探12306售票算法(二)-java代码实践

周五闲来无事,基于上一篇关于初探12306售票算法(一)-理论,进行了java编码实践供各位读者参考(以下为相关代码的简单描述) 1.订票工具类 1.1初始化一列车厢的票据信息 /** * 生成Ticket信息 * * @param train * @return */ public static List<Ticket> initTicketList(Train train) { List<Ticket> result = new ArrayList<Ticket>(

分享我对代码命名的一点思考和理解

一个软件最后都会落实到代码,而代码,其背后的架构设计或设计思想或模式固然重要,但我觉得更重要的东西则是良好的命名.混乱或错误的命名不仅让我们对代码难以理解,更糟糕的是,会误导我们的思维,导致对代码的理解完全错误.相反,良好的命名,则可以让我们的代码非常容易读懂,也能向读者正确表达事物以及逻辑的本质,从而使得代码的可维护性就大大增强. 另外一点也许大家还没感受到,那就是良好的命名,以及良好的命名习惯,由于我们总是对每个概念的名称要求非常苛刻,我们会思考这个名称所表达的概念是否正确,该名称是否正确表