ios响应式编程优缺点(响应式编程应用场景)

一直以来,都很想学学ReactiveCocoa这个神奇的技术,但是最后都由于各种原因搁置了。这次终于也认真的研究一番,把自己学习心得整理出来留个记录。

主要内容:

一、了解函数响应式编程二、ReactiveCocoa简介三、ReactiveCocoa集成四、理解什么是信号五、从源码理解RAC的信号机制六、本篇总结

一、了解函数响应式编程

ios响应式编程优缺点(响应式编程应用场景)

函数式编程(Funcational Programming)使用高阶函数编程,即函数可采用多种函数作为它们的参数和返回值。

响应式编程(Reactive Programming)一种面向数据流和变化传播的编程范式

函数响应式编程(Funcational Reacitve Programming)简称FRP,ReactiveCocoa就是一个典型的FRP框架,响应式的编程思想,函数式的代码形式。

二、ReactiveCocoa简介

ReactiveCocoa(简称RAC),Reactive表示响应式,Cocoa是苹果整个框架的简称,许多苹果框架都以Cocoa结尾。所以RAC是Github上为我们提供函数响应式编程方法的iOS开发框架。

三、ReactiveCocoa集成

https://github.com/ReactiveCocoa/ReactiveCocoa

通常,我们都使用Cocoapods集成RAC,需要注意的是Podfile文件中必须使用user_framework!,然后,针对于不同的代码环境,有三种集成情况:

1.纯OC工程

ReactiveObjc库包含原RAC2的全部代码,在纯OC工程中使用

platform:ios,’8.0’use_frameworks!#必须添加target’ZSTest’do#工程名pod’ReactiveObjC’#默认导入最新的RAC版本end2.纯Swift工程

纯Swfit工程继续使用ReactiveCocoa,但RAC依赖ReactiveSwift,所以相当于引入两个库。集成方法同上,只不过将ReactiveObjc换成ReactiveCocoa。

3.OC与Swift混编工程

混编工程需要同时引入ReactiveCocoa与ReactiveObjCBridge,但是ReactiveObjCBridge库依赖于ReactiveObjc库,所以相当于同时引入四个库了。示例如下:

platform:ios,’8.0’use_frameworks!#必须添加target’ZSTest’do#工程名pod’ReactiveCocoa’pod’ReactiveObjC’pod’ReactiveObjCBridge’end四、ReactiveCocoa信号理解

我觉得学习RAC的第一个关口就是理解信号RACSignal了,什么是信号也许是困惑我们的第一个问题。

作为RAC中最为核心的一个类,信号可以理解为传递数据变化信息的工具,信号会在数据发生变化时发送事件流给它的订阅者,然后订阅者执行响应方法。信号本身不具备发送信号的能力,而是交给一个订阅者去发出。

首先上一段代码,演示信号的一个基本使用。测试场景:我们要对一个用于输入用户名的UITextFiled进行检测,每次输入内容变化的时候都打出输入框的内容,使用RAC来实现此操作的关键代码如下:

[self.userNameTxtField.rac_textSignalsubscribeNext:^(NSString*_Nullablex){NSLog(@”测试:%@”,x);}];控制台打印:2018-03-2317:57:00.497956 0800ZSTest[4351:263810]测试:12018-03-2317:57:00.498237 0800ZSTest[4351:263810]测试:122018-03-2317:57:00.498375 0800ZSTest[4351:263810]测试:123

没错的,不使用代理方法,也没有action的响应处理,我们仅仅使用了一行方法就实现了对文本框输入内容的实时打印。由此,RAC的实用性可见一斑。

五、ReactiveCocoa信号机制

我们会对上面的代码产生疑问,RAC是怎么做到上述代码功能的呢?而且我们常说的订阅者又在哪里呢?

其实RAC已经使用Category的形式为我们基本的UI控件创建了信号(如上例中的rac_textSignal),所以这里我们才可以很方便的实现信号订阅,而且订阅者在整个过程中也是对于我们隐藏的。 现在我们使用自定义信号的方法,从创建信号到订阅信号细致的了解一下这个过程。首先上一段创建信号的测试代码如下:

//创建信号RACSignal*testSignal=[RACSignalcreateSignal:^RACDisposable*_Nullable(id<RACSubscriber>_Nonnullsubscriber){//1.订阅者发送信号内容[subscribersendNext:@”发送信号内容”];//2.订阅者发送信号完成的信息,不需要再发送数据时,最好发送信号完成,可以内部调起清理信号的操作。[subscribersendCompleted];//3.创建信号的Block参数,需要返回一个RACDisposable对象,可以返回nil。//RACDisposable对象用于取消订阅信号,此block在信号完成或者错误时调用。RACDisposable*racDisposable=[RACDisposabledisposableWithBlock:^{NSLog(@”信号Error或者Complete时销毁”);}];returnracDisposable;}];//订阅信号[testSignalsubscribeNext:^(id_Nullablex){//新变化的值NSLog(@”订阅信号:subscribeNext:%@”,x);}error:^(NSError*_Nullableerror){//信号错误,被取消订阅,被移除观察NSLog(@”订阅信号:Error:%@”,error.description);}completed:^{//信号已经完成,被取消订阅,被移除观察NSLog(@”订阅信号:subscribeComplete”);}];控制台打印:2018-03-2317:57:00.497956 0800ZSTest[4351:263810]订阅信号:subscribeNext:发送信号内容2018-03-2317:57:00.498237 0800ZSTest[4351:263810]订阅信号:subscribeComplete2018-03-2317:57:00.498375 0800ZSTest[4351:263810]信号Error或者Complete时销毁

我们通过观察源码来理解整个过程:

1.创建信号

创建信号,我们需要使用RACSignal的类方法createSignal。该方法需要一个Block作为参数。查看源码,我们就会发现RACSignal最终是通过调用自己子类RACDynamicSignal的createSignal方法,将这个Block设置给了自己的didSubscribe属性的。

//RACSignal.m文件 (RACSignal*)createSignal:(RACDisposable*(^)(id<RACSubscriber>subscriber))didSubscribe{return[RACDynamicSignalcreateSignal:didSubscribe];}//RACDynamicSignal.h文件@interfaceRACDynamicSignal()//Theblocktoinvokeforeachsubscriber.@property(nonatomic,copy,readonly)RACDisposable*(^didSubscribe)(id<RACSubscriber>subscriber);@end//RACDynamicSignal.m文件 (RACSignal*)createSignal:(RACDisposable*(^)(id<RACSubscriber>subscriber))didSubscribe{RACDynamicSignal*signal=[[selfalloc]init];signal->_didSubscribe=[didSubscribecopy];return[signalsetNameWithFormat:@” createSignal:”];}

didSubscribe:这是创建信号时候需要传入的一个block,它的传入参数是订阅者subscriber,而返回值是需要是一个RACDisposable对象。创建信号后的didSubscrib是一个等待执行的block。

RACSubscriber:表示订阅者,创建信号时订阅者发送信号,这里的订阅者是一个协议而非一个类。信号需要订阅者帮助其发送数据。查看RACSubscriber的协议,我可以看到以下几个方法:

//发送信息-(void)sendNext:(nullableid)value;//发送错误消息-(void)sendError:(nullableNSError*)error;//发送完成信息-(void)sendCompleted;//-(void)didSubscribeWithDisposable:(RACCompoundDisposable*)disposable;

在创建一个信号的时候,订阅者使用sendNext发送信息。而且如果我们不再发送数据,最好在这里执行一次sendCompleted方法,这样的话,信号内部会自动调用对应的方法取消信号订阅。

RACDisposable:这个类用于取消订阅信号和清理资源,在信号出现错误或者信号完成的时候,信号会自动调起RACDisposable对象的block方法。在代码中我们也可以看到,创建RACDisposable对象是使用disposableWithBlock方法设置了一个block操作,执行block操作之后,信号就不再被订阅了。

总结:创建信号就是使用createSignal方法,创建一个信号,并为信号设置了一个didSubscribe属性(也就是一系列订阅者需要做的操作)。

2.订阅信号

进入订阅信号的源码我们看到如下代码:

-(RACDisposable*)subscribeNext:(void(^)(idx))nextBlockerror:(void(^)(NSError*error))errorBlockcompleted:(void(^)(void))completedBlock{NSCParameterAssert(nextBlock!=NULL);NSCParameterAssert(errorBlock!=NULL);NSCParameterAssert(completedBlock!=NULL);RACSubscriber*o=[RACSubscribersubscriberWithNext:nextBlockerror:errorBlockcompleted:completedBlock];return[selfsubscribe:o];}

在此方法中,我们可以看到订阅信号有两个过程:过程1:使用subscribeNext的方法参数,创建出一个订阅者subscriber。过程2:信号对象执行了订阅操作subscribe,方法中传入参数是刚创建的订阅者。

注:这也就解释了我们常提起却看不见的订阅者存在哪里的问题。真实开发中我们只关心订阅者需要发送的值就行了,而不需要关心其内部订阅的过程。

继续打开信号的subscribe方法,看到源码如下:

-(RACDisposable*)subscribe:(id<RACSubscriber>)subscriber{NSCParameterAssert(subscriber!=nil);RACCompoundDisposable*disposable=[RACCompoundDisposablecompoundDisposable];subscriber=[[RACPassthroughSubscriberalloc]initWithSubscriber:subscribersignal:selfdisposable:disposable];if(self.didSubscribe!=NULL){RACDisposable*schedulingDisposable=[RACScheduler.subscriptionSchedulerschedule:^{RACDisposable*innerDisposable=self.didSubscribe(subscriber);[disposableaddDisposable:innerDisposable];}];[disposableaddDisposable:schedulingDisposable];}returndisposable;}

上面的代码中我们不难看出:除了对于订阅者和清理对象的再次封装外,最重要的就是创建信号时为信号设置Block(didSubscribe)被调用了,而且Block参数使用了我们创建的订阅者。

六、本篇总结

1.自创建信号会传入一个Block(didSubscribe),Block中遵循协议的订阅者会调用sendNext方法发送消息。而在订阅信号subscribeNext时,会在内部创建一个订阅者,并将其传递给原先赋值的didSubscribe,并执行这个Block。

2.但是我们应该注意:上述的分析只是其中信号机制的一种情况罢了。RAC对于UI组件信号的封装可能有所不同,比如之前我们看到的输入框信号,执行订阅信号subscribeNext时并不立即执行打印,而是监听到输入时打印。这其实是该信号使用了concat又做了一系列的操作。对于不同的信号我们只需要理解上述分析中提到几个关键属性,就可以结合源码很好的理解信号机制的使用了。

本篇的重点在于对RAC的基本介绍,是为了更好的理解信号机制,这仅相当于打开一个切入口来认识RAC。关于RAC更为详细用法可以参考下一篇:ReactiveCocoa函数响应式编程-应用篇。

发表评论

登录后才能评论