[ReactiveCocoa](翻译|巨坑)Basic Operators基本操作

使用ReactiveCocoa实现iOS平台响应式编程

ReactiveCocoa和响应式编程

在说ReactiveCocoa之前,先要介绍一下FRP(Functional Reactive Programming,响应式编程),在维基百科中有这样一个例子介绍:

在命令式编程环境中,a = b + c 表示将表达式的结果赋给a,而之后改变b或c的值不会影响a。但在响应式编程中,a的值会随着b或c的更新而更新。

Excel就是响应式编程的一个例子。单元格可以包含字面值或类似”=B1+C1″的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化 。

而ReactiveCocoa简称RAC,就是基于响应式编程思想的Objective-C实践,它是Github的一个开源项目,你可以在这里找到它。

关于FRP和ReactiveCocoa可以去看leezhong的这篇blog,图文并茂,讲的很好。

ReactiveCocoa框架概览

先来看一下leezhong再博文中提到的比喻,让你对有个ReactiveCocoa很好的理解:

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

下面我来逐一介绍ReactiveCocoa框架的每个组件

Streams

Streams表现为RACStream类,可以看做是水管里面流动的一系列玻璃球,它们有顺序的依次通过,在第一个玻璃球没有到达之前,你没法获得第二个玻璃球。
RACStream描述的就是这种线性流动玻璃球的形态,比较抽象,它本身的使用意义并不很大,一般会以signals或者sequences等这些更高层次的表现形态代替。

Signals

Signals表现为RACSignal类,就是前面提到水龙头,ReactiveCocoa的核心概念就是Signal,它一般表示未来要到达的值,想象玻璃球一个个从水龙头里出来,只有了接收方(subscriber)才能获取到这些玻璃球(value)。

Signal会发送下面三种事件给它的接受方(subscriber),想象成水龙头有个指示灯来汇报它的工作状态,接受方通过-subscribeNext:error:completed:对不同事件作出相应反应

  • next从水龙头里流出的新玻璃球(value)
  • error获取新的玻璃球发生了错误,一般要发送一个NSError对象,表明哪里错了
  • completed全部玻璃球已经顺利抵达,没有更多的玻璃球加入了

一个生命周期的Signal可以发送任意多个“next”事件,和一个“error”或者“completed”事件(当然“error”和“completed”只可能出现一种)

Subjects

subjects表现为RACSubject类,可以认为是“可变的(mutable)”信号/自定义信号,它是嫁接非RAC代码到Signals世界的桥梁,很有用。嗯。。。 这样讲还是很抽象,举个例子吧:

RACSubject *letters = [RACSubject subject]; RACSignal *signal = [letters sendNext:@"a"]; 

可以看到@"a"只是一个NSString对象,要想在水管里顺利流动,就要借RACSubject的力。

Commands

command表现为RACCommand类,偷个懒直接举个例子吧,比如一个简单的注册界面:

 RACSignal *formValid = [RACSignal combineLatest:@[ self.userNameField.rac_textSignal,self.emailField.rac_textSignal,] reduce:^(NSString *userName,NSString *email) { return @(userName.length > 0 && email.length > 0); }]; RACCommand *createAccountCommand = [RACCommand commandWithCanExecuteSignal:formValid]; RACSignal *networkResults = [[[createAccountCommand addSignalBlock:^RACSignal *(id value) { //... 网络交互代码 }] switchToLatest] deliverOn:[RACScheduler mainThreadScheduler]]; // 绑定创建按钮的 UI state 和点击事件 [[self.createButton rac_signalForControlEvents:UIControlEventTouchUpInside] executeCommand:createAccountCommand]; 

Sequences

sequence表现为RACSequence类,可以简单看做是RAC世界的NSArray,RAC增加了-rac_sequence方法,可以使诸如NSArray这些集合类(collection classes)直接转换为RACSequence来使用。

Schedulers

scheduler表现为RACScheduler类,类似于GCD,but schedulers support cancellationbut schedulers support cancellation,and always execute serially.

ReactiveCocoa的简单使用

实践出真知,下面就举一些简单的例子,一起看看RAC的使用

Subscription

接收-subscribeNext:-subscribeError:-subscribeCompleted:

RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal; // 依次输出 A B C D… [letters subscribeNext:^(NSString *x) { NSLog(@"%@",x); }]; 

Injecting effects

注入效果-doNext:-doError:-doCompleted:,看下面注释应该就明白了:

__block unsigned subscriptions = 0; RACSignal *loggingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { subscriptions++; [subscriber sendCompleted]; return nil; }]; // 不会输出任何东西 loggingSignal = [loggingSignal doCompleted:^{ NSLog(@"about to complete subscription %u",subscriptions); }]; // 输出: // about to complete subscription 1 // subscription 1 [loggingSignal subscribeCompleted:^{ NSLog(@"subscription %u",subscriptions); }]; 

Mapping

-map:映射,可以看做对玻璃球的变换、重新组装

RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence; // Contains: AA BB CC DD EE FF GG HH II RACSequence *mapped = [letters map:^(NSString *value) { return [value stringByAppendingString:value]; }]; 

Filtering

-filter:过滤,不符合要求的玻璃球不允许通过

RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; // Contains: 2 4 6 8 RACSequence *filtered = [numbers filter:^ BOOL (NSString *value) { return (value.intValue % 2) == 0; }]; 

Concatenating

-concat:把一个水管拼接到另一个水管之后

RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence; RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; // Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9 RACSequence *concatenated = [letters concat:numbers]; 

Flattening

-flatten:

Sequences are concatenated

RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence; RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; RACSequence *sequenceOfSequences = @[ letters,numbers ].rac_sequence; // Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9 RACSequence *flattened = [sequenceOfSequences flatten]; 

Signals are merged (merge可以理解成把几个水管的龙头合并成一个,哪个水管中的玻璃球哪个先到先吐哪个玻璃球)

RACSubject *letters = [RACSubject subject]; RACSubject *numbers = [RACSubject subject]; RACSignal *signalOfSignals = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { [subscriber sendNext:letters]; [subscriber sendNext:numbers]; [subscriber sendCompleted]; return nil; }]; RACSignal *flattened = [signalOfSignals flatten]; // Outputs: A 1 B C 2 [flattened subscribeNext:^(NSString *x) { NSLog(@"%@",x); }]; [letters sendNext:@"A"]; [numbers sendNext:@"1"]; [letters sendNext:@"B"]; [letters sendNext:@"C"]; [numbers sendNext:@"2"]; 

Mapping and flattening

-flattenMap:先 map 再 flatten

RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; // Contains: 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 RACSequence *extended = [numbers flattenMap:^(NSString *num) { return @[ num,num ].rac_sequence; }]; // Contains: 1_ 3_ 5_ 7_ 9_ RACSequence *edited = [numbers flattenMap:^(NSString *num) { if (num.intValue % 2 == 0) { return [RACSequence empty]; } else { NSString *newNum = [num stringByAppendingString:@"_"]; return [RACSequence return:newNum]; } }]; RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal; [[letters flattenMap:^(NSString *letter) { return [database saveEntriesForLetter:letter]; }] subscribeCompleted:^{ NSLog(@"All database entries saved successfully."); }]; 

Sequencing

-then:

RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal; // 新水龙头只包含: 1 2 3 4 5 6 7 8 9 // // 但当有接收时,仍会执行旧水龙头doNext的内容,所以也会输出 A B C D E F G H I RACSignal *sequenced = [[letters doNext:^(NSString *letter) { NSLog(@"%@",letter); }] then:^{ return [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence.signal; }]; 

Merging

+merge:前面在flatten中提到的水龙头的合并

RACSubject *letters = [RACSubject subject]; RACSubject *numbers = [RACSubject subject]; RACSignal *merged = [RACSignal merge:@[ letters,numbers ]]; // Outputs: A 1 B C 2 [merged subscribeNext:^(NSString *x) { NSLog(@"%@",sans-serif"> Combining latest values 

+combineLatest:任何时刻取每个水龙头吐出的最新的那个玻璃球

RACSubject *letters = [RACSubject subject]; RACSubject *numbers = [RACSubject subject]; RACSignal *combined = [RACSignal combineLatest:@[ letters,numbers ] reduce:^(NSString *letter,NSString *number) { return [letter stringByAppendingString:number]; }]; // Outputs: B1 B2 C2 C3 [combined subscribeNext:^(id x) { NSLog(@"%@",x); }]; [letters sendNext:@"A"]; [letters sendNext:@"B"]; [numbers sendNext:@"1"]; [numbers sendNext:@"2"]; [letters sendNext:@"C"]; [numbers sendNext:@"3"]; 

Switching

-switchToLatest:取指定的那个水龙头的吐出的最新玻璃球

RACSubject *letters = [RACSubject subject]; RACSubject *numbers = [RACSubject subject]; RACSubject *signalOfSignals = [RACSubject subject]; RACSignal *switched = [signalOfSignals switchToLatest]; // Outputs: A B 1 D [switched subscribeNext:^(NSString *x) { NSLog(@"%@",x); }]; [signalOfSignals sendNext:letters]; [letters sendNext:@"A"]; [letters sendNext:@"B"]; [signalOfSignals sendNext:numbers]; [letters sendNext:@"C"]; [numbers sendNext:@"1"]; [signalOfSignals sendNext:letters]; [numbers sendNext:@"2"]; [letters sendNext:@"D"]; 

常用宏

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); }]; 
RACObserve 监听属性的改变,使用block的KVO
 [RACObserve(self.textField,text) subscribeNext:^(NSString *newName) { NSLog(@"%@",newName); }]; 

UI Event

RAC为系统UI提供了很多category,非常棒,比如UITextView、UITextField文本框的改动rac_textSignal,UIButton的的按下rac_command等等。

最后

有了RAC,可以不用去操心值什么时候到达什么时候改变,只需要简单的进行数据来了之后的步骤就可以了。

说了这么多,在回过头去看leezhong的比喻该文最后总结的关系图,再好好梳理一下吧。我也是初学者,诚惶诚恐的呈上这篇博文,欢迎讨论,如有不正之处欢迎批评指正。

参考

https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/Documentation/FrameworkOverview.mdhttps://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/Documentation/BasicOperators.md
http://vimeo.com/65637501
http://iiiyu.com/2013/09/11/learning-ios-notes-twenty-eight/
http://blog.leezhong.com/ios/2013/06/19/frp-reactivecocoa.htmlhttp://nshipster.com/reactivecocoa/

iOS,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