ReactiveCocoa与Functional Reactive Programming

什么是Functional Reactive Programming

Functional Reactive Programming(以下简称FRP)是一种响应变化的编程范式。先来看一小段代码

a = 2
b = 2
c = a + b // c is 4

b = 3
// now what is the value of c?

如果使用FRP,c的值将会随着b的值改变而改变,所以叫做「响应式编程」。比较直观的例子就是Excel,当改变某一个单元格的内容时,该单元格相关的计算结果也会随之改变。

FRP提供了一种信号机制来实现这样的效果,通过信号来记录值的变化。信号可以被叠加、分割或合并。通过对信号的组合,就不需要去监听某个值或事件。

这在重交互的应用里是非常有用的。以注册为例:

提交按钮的状态要跟输入框的状态绑定,比如必选的输入框没有填完时,提交按钮是灰色的,也就是不可点;如果提交按钮不可点,那么文字变成灰色,不然变成蓝色;如果正在提交,那么输入框的文字颜色变成灰色,且不可点,不然变成默认色且可点;如果注册成功就在状态栏显示成功信息,不然显示错误信息,等等。

可以看到光是注册页就有这么多的联动,在javascript中可以采用事件监听来处理,iOS中更多的是delegate模式,本质上都是事件的分发和响应。这种做法的缺点是不够直观,尤其在逻辑比较复杂的情况下。这也是为什么尽管nodejs很高效,但由于javascript的callback style和异步模式不符合正常的编程习惯,让很多人望而却步。

使用FRP主要有两个好处:直观和灵活。直观的代码容易编写、阅读和维护,灵活的特性便于应对变态的需求。

ReactiveCocoa

ReactiveCocoa是github去年开源的一个项目,是在iOS平台上对FRP的实现。FRP的核心是信号,信号在ReactiveCocoa(以下简称RAC)中是通过RACSignal来表示的,信号是数据流,可以被绑定和传递。

可以把信号想象成水龙头,只不过里面不是水,而是玻璃球(value),直径跟水管的内径一样,这样就能保证玻璃球是依次排列,不会出现并排的情况(数据都是线性处理的,不会出现并发情况)。水龙头的开关默认是关的,除非有了接收方(subscriber),才会打开。这样只要有新的玻璃球进来,就会自动传送给接收方。可以在水龙头上加一个过滤嘴(filter),不符合的不让通过,也可以加一个改动装置,把球改变成符合自己的需求(map)。也可以把多个水龙头合并成一个新的水龙头(combineLatest:reduce:),这样只要其中的一个水龙头有玻璃球出来,这个新合并的水龙头就会得到这个球。

下面通过一个简单的demo来演示这个模型。

假如对象的某个属性想绑定某个消息,可以使用RAC这个宏,相当于给玻璃球套了一个水龙头。

RAC(self.submitButton, enabled) = [RACSignal combineLatest:@[self.usernameField.rac_textSignal, self.passwordField.rac_textSignal] reduce:^id(NSString *userName, NSString *password) { return @(userName.length >= 6 && password.length >= 6); }];

这样,如果用户名和密码框的长度都超过6,提交按钮就enable了。反之,如果没符合要求,就会处于非开启状态。

可以看到usernameField有了一个新的属性rac_textSignal,这是RAC在UITextFieldcategory中添加的,直接用即可。

RAC的大统一

RAC统一了对KVO、UI Event、Network request、Async work的处理,因为它们本质上都是值的变化(Values over time)。

KVO

RAC可以用来监测属性的改变,这点跟KVO很像,不过使用了block,而不是-observeValueForKeyPath:ofObject:change:context:

[RACAble(self.username) subscribeNext:^(NSString *newName) { NSLog(@"%@", newName); }];

使用起来是不是比KVO舒服多了。比KVO更加强大的是信号可以被链起来(chain)

// 只有当名字以'j'开头,才会被记录 [[RACAble(self.username) filter:^(NSString *newName) { return [newName hasPrefix:@"j"]; }] subscribeNext:^(NSString *newName) { NSLog(newName); }];

UI Event

RAC还为系统UI提供了很多category,来方便消息的创建和传递,比如按钮被点击或文本框有改动等等,上面的例子中self.firstNameField.rac_textSignal,在对应的文本框有改动时,会自动向数据流中添加新的数据,绑定该消息的其他消息就会收到新的数据,如果有subscriber的话,会自动触发。

Network Request && Async work

这些可以通过自定义信号,也就是RACSubject(继承自RACSignal,可以理解为自由度更高的signal)来搞定。比如一个异步网络操作,可以返回一个subject,然后将这个subject绑定到一个subscriber或另一个信号。

- (void)doTest { RACSubject *subject = [self doRequest]; [subject subscribeNext:^(NSString *value){ NSLog(@"value:%@", value); }]; } - (RACSubject *)doRequest { RACSubject *subject = [RACSubject subject]; // 模拟2秒后得到请求内容 // 只触发1次 // 尽管subscribeNext什么也没做,但如果没有的话map是不会执行的 // subscribeNext就是定义了一个接收体 [[[[RACSignal interval:2] take:1] map:^id(id _){ // the value is from url request NSString *value = @"content fetched from web"; [subject sendNext:value]; return nil; }] subscribeNext:^(id _){}]; return subject; }

小结

简单画了下关系图,罗列了些要点

上面只是大概说了一下RAC的使用情景和用法,更多的例子可以到项目主页中查看。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 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