ReactiveCocoa(简称RAC,以下都用RAC)是github团队开源的一套基于Cocoa并且具有FRP(Functional Reactive Programming-响应式编程)特性的框架。RAC本身就是一个第三方类库,使用它可以大大提高开发效率,简化代码,目前在各个公司也在大范围使用。RAC比较复杂,在正式介绍之前,先看一下它的类图,以便大致了解层次结构。

RAC主要包含了四个组件

  • 信号源方面:RACStream及其子类
  • 订阅者方面:RACSubscriber及其子类
  • 调度器方面:RACScheduler及其子类
  • 清洁工方面:RACDisposable及其子类

在RAC中,信号源是最核心的部分,其工作过程是:创建信号--订阅信号--发送信号。

拓展:响应式编程(FRP)

在命令式编程中,a = b + c代表是b与c的加和结果赋值给a,如果之后再改变b或者c的值并不会影响a。但是在响应式编程中,a的值会随着b或者c的变化而变化,也就是a的结果和b与存在绑定关系,b或者c的变化会直接影响a。这就是响应式编程(FRP),举个简单的例子。

信号源

信号分为冷信号和热信号。

理解冷信号和热信号的区别对RAC的理解有非常大的帮助,下面我们重点讲解这:

  • Hot Observable(热信号)是主动的,即使你没有订阅信号,它也会时可推送,例如鼠标移动;而Cold Observable(冷信号)是被动的,也就是只有你订阅信号,它才会发布消息,反之不然。
  • Hot Observable(热信号)可以有多个订阅者,是一对多,整个集合可以与订阅者共享信息;而Cold Observable只能一对一,遇到有不同的订阅者,消息是重新完整发送的。

在RAC中除了RACSubject和其子类是热信号,剩下的就是冷信号。RACSubject和其子类类似直播,错过之后也就不会处理了;而signal类似点播,每次发送订阅,都是从头开始。

Subject具备如下特点:

  • Subject是非RAC到RAC的桥梁
  • Subject可以附加行为:RACReplaySubject具备为订阅者缓冲事件的能力。

为了大家更好理解两者的区别,如下:

//创建热信号
RACSubject *subject = [RACSubject subject];
[subject sendNext:@]; //立即发送1
[[RACScheduler mainThreadScheduler] afterDelay:0.5 schedule:^{
[subject sendNext:@]; //0.5秒后发送2 }]; [[RACScheduler mainThreadScheduler] afterDelay: schedule:^{
[subject sendNext:@]; //2秒后发送3 }];
[[RACScheduler mainThreadScheduler] afterDelay:0.1 schedule:^{
[subject subscribeNext:^(id x) {
NSLog(@"subject1接收到了%@",x); //0.1秒后subject1订阅了 }];
}];
[[RACScheduler mainThreadScheduler] afterDelay: schedule:^{
[subject subscribeNext:^(id x) {
NSLog(@"subject2接收到了%@",x); //1秒后subject2订阅了 }];
}];
//创建冷信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@];
[[RACScheduler mainThreadScheduler] afterDelay:0.5 schedule:^{
[subscriber sendNext:@];
}];
[[RACScheduler mainThreadScheduler] afterDelay: schedule:^{
[subscriber sendNext:@];
}]; return nil;
}]; [[RACScheduler mainThreadScheduler] afterDelay:0.1 schedule:^{
[signal subscribeNext:^(id x) { NSLog(@"signal1接收到了%@", x);
}];
}];
[[RACScheduler mainThreadScheduler] afterDelay: schedule:^{
[signal subscribeNext:^(id x) { NSLog(@"signal2接收到了%@", x);
}];
}];

通过运行结果:

从上面运行结果发现:

  • 0.1秒后订阅的subject1接收到了0.5秒后2秒后发送的信号,没有接收到之前发送的新号。

  • 1秒后订阅的subject2接收到了2秒后发送的信号,也没有接收到之前发送的新号。

  • signal1和signal2都接收到了所有信号。

从上面的运行结果总结

热信号是主动的,即使没有订阅事件,仍然会时刻推送;而冷信号是被动的,只有当你订阅的时候,它才会发送消息。

热信号是可以有多个订阅者,一对多,信号是可以与订阅者相互共享信息的。在第一段代码,两个订阅者是共享的,他们在同一时间接收到3个值,而冷信号只能一对一,当有不同的订阅者,消息都会从新完整发送。

使用信号常见的问题:

1.多次订阅

对RAC的信号进行转换的时候,其实就是对原有的信号进行订阅从而产生新的信号。如下代码所示:

RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) { NSLog(@"来了"); //网络请求,产生model [subscriber sendNext:model]; return nil;
}]; RACSignal *name = [signal flattenMap:^RACStream *(Person *model) { return [RACSignal return:model.name];
}];
RACSignal *age = [signal flattenMap:^RACStream *(Person *model) { return [RACSignal return:model.age];
}]; RAC(self.userNameTextFiled,text) = [[name catchTo:[RACSignal return:@"error"]] startWith:@"name:"];
RAC(self.passwordTextField,text) = [[age catchTo:[RACSignal return:@"error"]] startWith:@"age:"];

上面分别对model进行了map,也就是产生了两个新的信号,然后再对两个信号进行订阅,对这两个信号订阅的时候,也会对间接对原信号进行订阅,从而造成对原信号的多次订阅,如上所示来了就输出了三次,如果是网络请求的话,也会输出三次,所以一定在信号转换的时候一定要注意这些情况。

RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id subscriber) { NSLog(@"来了");
[subscriber sendNext:model]; return nil;
}] replayLazily]; //转换为热信号
RACSignal *name = [signal flattenMap:^RACStream *(Person *model) { return [RACSignal return:model.name];
}];
RACSignal *age = [signal flattenMap:^RACStream *(Person *model) { return [RACSignal return:model.age];
}]; RAC(self.userNameTextFiled,text) = [[name catchTo:[RACSignal return:@"error"]] startWith:@"name:"];
RAC(self.passwordTextField,text) = [[age catchTo:[RACSignal return:@"error"]] startWith:@"age:"];

2、内存泄露

RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) { //1 Person *model = [[Person alloc] init];
[subscriber sendNext:model];
[subscriber sendCompleted]; return nil;
}]; self.flattenMapSignal = [signal flattenMap:^RACStream *(Person *model) { //2 return RACObserve(model, name);
}];
[self.flattenMapSignal subscribeNext:^(id x) { //3 NSLog(@"recieve - %@", x);
}];

如上代码,看起来工作正常,但你使用内存检测工具会发现,这里会造成内存泄漏,原因就是

#define RACObserve(TARGET, KEYPATH) \ ({ \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wreceiver-is-weak\"") \
__weak id target_ = (TARGET); \
[target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \
_Pragma("clang diagnostic pop") \
})

这段代码,所以这里的Block引用了self,就造成了循环引用。
解决办法也很简单,使用@weakify和@strongify即可:

RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
Person *model = [[Person alloc] init];
[subscriber sendNext:model];
[subscriber sendCompleted]; return nil;
}];
@weakify(self); self.flattenMapSignal = [signal flattenMap:^RACStream *(Person *model) {
@strongify(self); return RACObserve(model, name);
}];
[self.flattenMapSignal subscribeNext:^(id x) { NSLog(@"recieve - %@", x);
}];

调度器:RACScheduler 在 ReactiveCocoa 中就是扮演着调度器的角色,本质上,它就是用 GCD 的串行队列来实现的,并且支持取消操作。是的,在 ReactiveCocoa 中,并没有使用到 NSOperationQueue 和 NSRunloop 等技术,RACScheduler 也只是对 GCD 的简单封装而已。

清洁工:RACDisposable 在 ReactiveCocoa 中就充当着清洁工的角色,它封装了取消和清理一次订阅所必需的工作。它有一个核心的方法 -dispose ,调用这个方法就会执行相应的清理工作,这有点类似于 NSObject 的 -dealloc 方法。

以后博客也将继续介绍RAC的基本框架。

RAC(ReactiveCocoa)概括的更多相关文章

  1. RAC(ReactiveCocoa)使用方法(一)

    RAC(ReactiveCocoa)使用方法(一) RAC(ReactiveCocoa)使用方法(二) 什么是RAC? 最近回顾了一下ReactiveCocoa的方法,也看了一些人的文章,现写篇文章总 ...

  2. RAC(ReactiveCocoa)使用方法(二)

    RAC(ReactiveCocoa)使用方法(一) RAC(ReactiveCocoa)使用方法(二) 上篇文章:RAC(ReactiveCocoa)使用方法(一) 中主要介绍了一些RAC中常见类的用 ...

  3. RAC(ReactiveCocoa)介绍(一)

    最近在学习RAC,之前在iOS工作中,类之间的传值,无非是block.delegate代理.KVO和Notification等这几种方法.在RAC中,同样具备替代block.delegate代理.KV ...

  4. ReactiveCocoa基础知识内容

    本文记录一些关于学习ReactiveCocoa基础知识内容,对于ReactiveCocoa相关的概念如果不了解可以网上搜索:RACSignal有很多方法可以来订阅不同的事件类型,ReactiveCoc ...

  5. [干货分享]一篇可能会让你爱上MVVM与ReactiveCocoa的文章

    概要 在此工程中,本文将讨论将MVC改造为MVVM需要的一些基本方法,同时会适当穿插部分关于MVVM概念性的讨论!本文最大的意义在于,提供了一种读者可以复现的方式,逐步引出从MVC向MVVM尽可能平滑 ...

  6. 李洪强iOS经典面试题下

    李洪强iOS经典面试题下 21. 下面的代码输出什么? @implementation Son : Father - (id)init { self = [super init]; if (self) ...

  7. fix LayerKit framework不能提交App Store

    - 问题: - 原因 x86_64, i386是ios模拟器用的architectures.发布时,不支持这两种.但是,默认编译出来的layerkit framework支持这两种编译器 - 解决办法 ...

  8. IOS响应式编程框架ReactiveCocoa(RAC)使用示例

    ReactiveCocoa是响应式编程(FRP)在iOS中的一个实现框架,它的开源地址为:https://github.com/ReactiveCocoa/ReactiveCocoa# :在网上看了几 ...

  9. IOS响应式编程框架ReactiveCocoa(RAC)使用示例-备

    ReactiveCocoa是响应式编程(FRP)在IOS中的一个实现框架,它的开源地址为:https://github.com/ReactiveCocoa/ReactiveCocoa# :在网上看了几 ...

随机推荐

  1. 阿里巴巴Java开发程序猿年薪40W是什么水平?

    对于年薪40万的程序员,不只是技术过硬,还有一个原因是他们所在的公司福利高,或者会直接持股.在BAT中就是一个很好的案例,例如阿里巴巴P7,P8级别的员工不仅是年薪30到80万不等,还有更多股票持有. ...

  2. 华盛顿邮报:FBI 屡次夸大了“手机加密威胁”的数字

    <华盛顿邮报>周二报道称,美国联邦调查局(FBI)严重夸大了由加密手机所造成的问题.以去年为例,该机构调查人员声称被大约 7800 部涉嫌犯罪活动的加密设备挡在了门外,而准确的数字应该在 ...

  3. 使用Onenote & Evernote & VSC+Markdown构建个人笔记系统

    Onenote & Evernote & VSC+Markdown构建个人笔记系统 umeowbing(转载请注明出处) 1 Why 笔记本太多,全部带着太重,查找起来也很麻烦-- 笔 ...

  4. OsharpNS轻量级.net core快速开发框架简明入门教程-Osharp.Redis使用

    OsharpNS轻量级.net core快速开发框架简明入门教程 教程目录 从零开始启动Osharp 1.1. 使用OsharpNS项目模板创建项目 1.2. 配置数据库连接串并启动项目 1.3. O ...

  5. dotnet-warp && NSSM 部署 .net core 项目到 windows 服务

    如果你想将 .net core 项目以服务的形式部署到 windows 系统,希望本篇文章能够让你少走弯路 dotnet-warp 安装使用 dotnet-warp 是一个全局的.NET Core 工 ...

  6. Spark学习之数据读取与保存总结(二)

    8.Hadoop输入输出格式 除了 Spark 封装的格式之外,也可以与任何 Hadoop 支持的格式交互.Spark 支持新旧两套Hadoop 文件 API,提供了很大的灵活性. 要使用新版的 Ha ...

  7. 吴恩达深度学习笔记1-神经网络的编程基础(Basics of Neural Network programming)

    一:二分类(Binary Classification) 逻辑回归是一个用于二分类(binary classification)的算法.在二分类问题中,我们的目标就是习得一个分类器,它以对象的特征向量 ...

  8. 使用NSSM把.Net Core部署至 Windows 服务

    为什么部署至Windows Services 在很多情况下,很少会把.Net Core项目部署至Windows服务中,特别是Asp.net Core就更少了.一般情况下,Asp.net Core会部署 ...

  9. 【递归打卡2】求两个有序数组的第K小数

    [题目] 给定两个有序数组arr1和arr2,已知两个数组的长度分别为 m1 和 m2,求两个数组中的第 K 小数.要求时间复杂度O(log(m1 + m2)). [举例] 例如 arr1 = [1, ...

  10. Java设置PPT幻灯片背景——纯色、渐变、图片背景

    PPT幻灯片生成时,系统默认是无色背景填充,幻灯片设计需要手动设置背景效果,可设置颜色填充或者图片背景填充.本文将对此介绍具体实现方法. 使用工具:Free Spire.Presentation fo ...