ReactiveCocoa 入门指导

原文地址:http://www.teehanlax.com/blog/getting-started-with-reactivecocoa/


在先前的文章中,介绍一了ReactiveCocoa概念,ReactiveCocoaObjective-C中用于声明式编程的类库。接下来在这里会介绍一些ReactiveCocoa中的模式,讨论一些最佳实践,并指出一些常见的陷阱。ReactiveCocoa的学习需要时间,让我们慢慢来。


模式

ReactiveCocoa中有三种基本的模式:责任链、分割和组合模式(chaining,splitting,and combining)。之前的文章时稍微介绍了责任链和组合模式,接下来的会更深入。


回顾一下:在ReactiveCocoa中的核心是signal:(信号),它表示不断变化的状态。当我们使用chainsplitcombine时,实际上我们就是在操作这些signal


Chaining是在ReactiveCocoa最常用的模式:将一个已有的signal转换为一个新的signal。常用的操作是创建一个新的signal,再对它使用filter:map:startWith:方法例:

[objc] view plain copy
  1. RAC(self.textField.text)=[[[RACSignalinterval:1]startWith:[NSDatedate]]map:^id(NSDate*value){
  2. NSDateComponents*dateComponents=[[NSCalendarcurrentCalendar]components:NSMinuteCalendarUnit|NSSecondCalendarUnitfromDate:value];
  3. return[NsstringstringWithFormat:@"%d:%02d",dateComponents.minute,dateComponents.second];
  4. }];

在这个例子中,我们将textFiledtext属性绑定为三个串连的signals的结果。首先,我们创建一个间隔信号,这个信号每隔一秒钟就发送当前时间。间隔信号在没有启动的时候是不会有值的,所以我们使用startWith:启动起来。最后,使用map:signalNSDate值转换为一个Nsstring字符串,这个字符串将会被赋值到textFieldtext属性上。



Chaining是最常用的操作,而且它通常不使用局部变量,而是像上面那样串连起来操作。下面的代码与上面的代码是等同的。

    RACSignal*intervalSignal=[RACSignalinterval:1];
  1. RACSignal*startedIntervalSignal=[intervalSignaldate]];
  2. RACSignal*mappedIntervalSignal=[startedIntervalSignal }];
  3. RAC(self.textField.text)=mappedIntervalSignal;

Splittingchaining比较类似,也是将signal转换为其它的sginal,不同之处在于,Splitting会重复使用中间的signalsSplitting看起来要复杂些,其实也就是一个signals使用多次罢了。例:

    RACSignal*dateComponentsSignal=[[[RACSignalreturndateComponents;
  1. }];
  2. RAC(self.minuteTextField.text)=[dateComponentsSignalNSDateComponents*dateComponents){
  3. stringWithFormat:@"%d",dateComponents.minute];
  4. self.secondTextField.text)=[dateComponentsSignal.second];
  5. }];


在上面这个例子中,创建了一个串联signal,即局部变量:dateComponentsSignal。接着再用dateComponentsSignal创建两个新的signal,并将它们分别与两个textfield的text属性进行绑定。



第三种常用模式是combining。combining就是将几个signal结合起来创建出一个新的signal。比如“登录”按钮,只有在“用户名”与“密码”输入框中的文本长度都超过6时才能被点击,否则处于不可用的状态。那么我们可以为“登录”按钮的enabled状态创建一个signal,这个signal则是由“用户名”与“密码”框它们两个自己的signal组合起来

    self.submitButton.enabled)=[RACSignalcombineLatest:@[self.usernameField.rac_textSignal,self.passwordField.rac_textSignal]reduce:^Nsstring*userName,153); background-color:inherit; font-weight:bold">Nsstring*password){
  1. return@(userName.length>=6&&password.length>=6);
  2. }];

在这里,我们将“登录”按钮的enable状态绑定到使用combineLatest:reduce:方法创建的signal上。这个方法的第二个参数是一个block,这个block的参数是combineLatest中的参数的最新值的组合。我们将两个文本框的text signal一起传到combineLatest,在reduce的block中,该block也就会接收到两个Nsstring的参数,这个block的工作就是将两个参数值组合起来生成一个值,然后返回。该方法的说明:

// +combineLatest:reduce: takes an array of signals,executes the block with the

// latest value from each signal whenever any of them changes,and returns a new

// RACSignal that sends the return value of that block as values.




Combining常用于两种情况:

1、需要同时满足多种条件。

2、在多个signal中进行选择。


重点在于这种线性逻辑(linear flow of logic)的思维,如何将这些signals进行串联、分割或组合。看看这些基本操作能让你对这些模式更加熟悉。

最佳实践

我们已经介绍了ReactiveCocoa模式的基本知识,接下来看看最佳实践。


ReactiveCocoa通过移除状态使我们写程序更容易。然而,即使是在一个“完成反应(completely reactive)”式的应用中,我们还是得写些非ReactiveCocoa的代码,比如像table view的delegate方法。RACSubjects则充当了非reactive和 reactive代码的桥梁。

RACSubject是能够手动发送新值的signal。比如,gesture recognizers并不是ReactiveCocoa的一部分——这时我们可以用两个RACSubject属性一个用于接收gesture recognizer:事件,标识这个recognizer是否正在处理;另一个用来记录它当前的位置。

    self.gestureRecognizerIsRunningSubject=[RACSubjectsubject];
  1. self.gestureRecognizerValueSubject=[RACSubjectsubject];
  2. self.someView.frame)=[self.gestureRecognizerValueSubjectNSValue*value){
  3. CGPointlocation=[valueCGPointValue];
  4. CGFloatsize=100.0f;
  5. return[NSValuevalueWithCGRect:CGRectMake(location.x-size/2.0f,location.y-size/2.0f,size,size)];
  6. 我们将一个view放到gesture recognizer的最后位置的中心。


    发送这些subjects事件非常简单,只要简单实现一个gesture recognizer方法即可(注:该方法可放到代理方法-gestureRecognizer:shouldReceivetouch:中调用):

      -(void)gestureRecognizerReceivedTouch:(UIPanGestureRecognizer*)recognizer{
    1. if(recognizer.state==UIGestureRecognizerStateBegan){
    2. [self.gestureRecognizerIsRunningSubjectsendNext:@(YES)];
    3. }
    4. elseif(recognizer.state==UIGestureRecognizerStateChanged){
    5. sendNext:[NSValuevalueWithCGPoint:[recognizerlocationInView:self.view]]];
    6. if(recognizer.state==UIGestureRecognizerStateEnded){
    7. NO)];
    8. }

    虽然RACSubjects是非reactive代码与ReactiveCocoa代码的桥梁,但过分滥用也是有风险的。当我们能够通过chaining signals完成任务的话,就不要依赖于RACSubjects的值。

    ReactiveCocoa的设计就是让我们的程序尽可能地减少各种状态,这种减少状态的逻辑使得我们要少用执行副作用(perform side-effects)。比如基于上面的代码,我们希望在table view的手势事件完成时,闪烁一个滑动条,让用户知道已经滑到哪里了。我们可以调用subscription:

      [[filter:^BOOL(NSNumber*gestureRecognizerIsRunning){
    1. return!(gestureRecognizerIsRunning.boolValue);
    2. }]subscribeNext:^(idx){
    3. [self.tableViewflashScrollIndicators];
    4. 这里我们过滤掉手势正在运行的事件,只在滑动完成时subcribe。这时,在subscribeNext: bloc中,我们执行了副作用。

      虽然subscriptions很有用,执行副作用也是必要的,但要小心过度使用。它们就是可变的变量、状态,这些正是ReactiveCocoa所避免的。在能够通过绑定属性映射signals完成任务的时候,就不要使用RACSubjects。


      陷阱

      任何一种新的东西,对于新手来说总会有些陷阱。比如下面的代码,当我们从一个属性创建一个新的signal,在someString的值改变之前,其实是什么也不会发生的。

        self.label.text)=RACAble(self.someString);

      如果想要立即发送someSting当前的值,可以用RACAbleWithStart。这个“starts”的signal会将someString的当前的值也与之绑定。

        self.label.text)=RACAbleWithStart(self.someString);

      与之类似,当使用interval:安排一个周期定时器时,这个定时器不会立即启用走到这个传来第一个interval,有点像用NSTimer。还记得第一个例子不?我们将text field的text值与当前时间绑定,我们是手动使用startWith:并传当前时间来开启的。如果我们不这么做的话,text field在第一个间隔时间的前一秒是空的。


      关于interval:还有一个重点需要注意的时,这个方法的将它的结果传递到高优化级的调度中(类似于GCD队列)。也就是说之前的代码实际上有一个微秒的BUG的:不能直接执行更新UI的代码。可以将interval: signal的结果传递到主线程调度中结果这个问题:

        self.textField.text)=[[[[RACSignalcomponents:(NSMinuteCalendarUnit|NSSecondCalendarUnit)deliverOn:[RACSchedulermainThreadScheduler]];

      这样就好点了。

      这篇文章介绍了一些常用模式,最佳实践还有一些陷阱。希望能帮助你利用ReactiveCocoa构建声明式应用程序。

      版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

      相关推荐


      react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
      我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
      我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
      react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
      mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
      我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
      本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
      上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
      上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
      我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
      在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
      楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
      我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
      前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
      我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
      上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
      开源不易,感谢你的支持,❤ star me if you like concent ^_^
      hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
      本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
      ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom