前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ReactiveCocoa函数响应式编程-基础篇目录:一、了解函数响应式编程二、ReactiveCocoa简介三、ReactiveCocoa集成四、ReactiveCocoa信号理解五、Reactiv

ReactiveCocoa函数响应式编程-基础篇目录:一、了解函数响应式编程二、ReactiveCocoa简介三、ReactiveCocoa集成四、ReactiveCocoa信号理解五、Reactiv

作者头像
梧雨北辰
发布2018-04-24 14:52:51
6620
发布2018-04-24 14:52:51
举报

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

目录:

一、了解函数响应式编程

二、ReactiveCocoa简介

三、ReactiveCocoa集成

四、理解什么是信号

五、从源码理解RAC的信号机制

六、本篇总结

一、了解函数响应式编程

image.png

函数式编程(Funcational Programming)

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

响应式编程(Reactive Programming)

一种面向数据流和变化传播的编程范式

函数响应式编程(Funcational Reacitve Programming)

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

二、ReactiveCocoa简介

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

iOS开发中,我们需要使用按钮点击、代理、通知等这些方法来处理响应事件。而RAC框架使用Category为很多基本的UIKit控件添加信号Signal,这样我们可以通过信号来监听数据流与变化传播,把将监听的代码与处理代码放在一起,从而方便我们管理。利用此特点结合MVVM架构,RAC也有十分显著的作用。

三、ReactiveCocoa集成

RAC.5.0相对于之前版本对于自身项目结构进行了较大调整,被拆分ReactiveCocoa、ReactiveSwift、ReactiveObjC、ReactiveObjCBridge四个库,我们需要根据不同的情况来集成。

GitHub地址:https://github.com/ReactiveCocoa/ReactiveCocoa

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

1.纯OC工程

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

代码语言:javascript
复制
platform :ios, '8.0'
use_frameworks!     #必须添加
target 'ZSTest' do  #工程名
#pod 'ReactiveObjC' #默认导入最新的RAC版本         
end
2.纯Swift工程

纯Swfit工程继续使用ReactiveCocoa,但RAC依赖ReactiveSwift,所以相当于引入两个库。

集成方法同上,只不过将ReactiveObjc换成ReactiveCocoa。

3.OC与Swift混编工程

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

代码语言:javascript
复制
platform :ios, '8.0'
use_frameworks!     #必须添加
target 'ZSTest' do  #工程名
pod 'ReactiveCocoa'
pod 'ReactiveObjC'  
pod 'ReactiveObjCBridge'
end

四、ReactiveCocoa信号理解

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

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

首先上一段代码,演示信号的一个基本使用。

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

代码语言:javascript
复制
[self.userNameTxtField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"测试:%@",x);
}];
代码语言:javascript
复制
控制台打印:
2018-03-23 17:57:00.497956+0800 ZSTest[4351:263810] 测试:1
2018-03-23 17:57:00.498237+0800 ZSTest[4351:263810] 测试:12
2018-03-23 17:57:00.498375+0800 ZSTest[4351:263810] 测试:123

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

五、ReactiveCocoa信号机制

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

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

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

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

1.创建信号

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

代码语言:javascript
复制
//RACSignal.m文件
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    return [RACDynamicSignal createSignal:didSubscribe];
}
代码语言:javascript
复制
//RACDynamicSignal.h文件
@interface RACDynamicSignal ()
// The block to invoke for each subscriber.
@property (nonatomic, copy, readonly) RACDisposable * (^didSubscribe)(id<RACSubscriber> subscriber);
@end
代码语言:javascript
复制
//RACDynamicSignal.m文件
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    RACDynamicSignal *signal = [[self alloc] init];
    signal->_didSubscribe = [didSubscribe copy];
    return [signal setNameWithFormat:@"+createSignal:"];
}

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

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

代码语言:javascript
复制
//发送信息
- (void)sendNext:(nullable id)value;
//发送错误消息
- (void)sendError:(nullable NSError *)error;
//发送完成信息
- (void)sendCompleted;
//
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable;

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

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

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

2.订阅信号

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

代码语言:javascript
复制
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock {
    NSCParameterAssert(nextBlock != NULL);
    NSCParameterAssert(errorBlock != NULL);
    NSCParameterAssert(completedBlock != NULL);
    
    RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:errorBlock completed:completedBlock];
    return [self subscribe:o];
}

在此方法中,我们可以看到订阅信号有两个过程:

过程1:使用subscribeNext的方法参数,创建出一个订阅者subscriber。

过程2:信号对象执行了订阅操作subscribe,方法中传入参数是刚创建的订阅者。

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

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

代码语言:javascript
复制
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);
    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
    subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
    if (self.didSubscribe != NULL) {
        RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
            RACDisposable *innerDisposable = self.didSubscribe(subscriber);
            [disposable addDisposable:innerDisposable];
        }];
        [disposable addDisposable:schedulingDisposable];
    }
    return disposable;
}

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

六、本篇总结

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

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

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

其他参考链接:

1.ReactiveCocoa入门教程:第一部分

2.最快让你上手ReactiveCocoa之基础篇

3.这样好用的ReactiveCocoa,根本停不下来

4.函数式编程与面向对象的比较

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018.03.29 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录:
  • 一、了解函数响应式编程
  • 二、ReactiveCocoa简介
  • 三、ReactiveCocoa集成
  • 四、ReactiveCocoa信号理解
  • 五、ReactiveCocoa信号机制
  • 六、本篇总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档