RAC---学习

前言

使用WWHA(Why/What/How/Attention)方法,学习RAC

  1. 为什么用RAC?
  2. 什么是RAC?
  3. 怎么用RAC?
  4. 使用RAC需要注意什么?

在学习过程中,别人的博客里面有这句话:
在这里插入图片描述
看来,学习一个知识,还是要从WWHA方向学习


1. 为什么用RAC?

为什么用RAC?
ReactiveCocoa 试图解决以下问题:

  1. 传统 iOS 开发过程中,状态以及状态之间依赖过多的问题
  2. 传统 MVC 架构的问题:Controller 比较复杂,可测试性差
1. 解决状态以及状态之间依赖过多问题

比如需要监听登录的两个输入框,控制login按钮是否可点击,就需要写在好几处地方
使用RAC,可以在一个地方写出想要的效果

2. 解决MVC中Controller复杂性

MVC中,Controller所以逻辑、网络都在Controller中,会造成Controller很大,不以利维护与调试

MVVM中,ViewModel做了Controller的部分工作;

使用RAC,可以将ViewModel与View关联,View 层的变化可以直接响应 ViewModel 层的变化
View 不再与 Model 绑定,也增加了 View 的可重用性


2. 什么是RAC?

RAC全称为ReactiveCocoa,是由 Github 工程师们开发的一个应用于 iOS 和 OS X 开发的函数响应式编程新框架;ReactiveCocoa 为开发者带来了函数式编程响应式编程的思想
ReactiveCocoa官网

KVO、代理、通知、Block调用(Block本身不是,Block调用是)都是响应式

Reactive Cocoa是函数式编程(Functional Programing)(FP)思想的实现
那么,什么是函数式编程呢?

什么是函数式编程?

  • 面向过程编程procedure oriented Programming(POP)
  • 面向对象编程Object Oriented Programming(OOP)
  • 响应式编程Reactive Programming(RP)
  • 函数式编程Functional Programming(FP)
  • 函数响应式编程Functional Reactive Programming(FRP)

你能不能分清?

响应式和函数式,两个容易混淆的概念

像计算函数表达式一样来解决一个问题
比如:已知f(x) = 2sin(x + π/2), 求 f(π/2)的值
这个函数可以分为几个小函数:

f1(x) = x + π/2
f2(x) = sin(x)
f3(x) = 2x

那么,原来的函数表达式可以转化为:
f(x) = f3(f2(f1(x)))

也就是,一个复杂的函数,可以分解为N个小的基本函数
或者说N个小的基本函数,可以合成复杂函数

函数的组成三要素:函数名、参数、返回值

对于像上面的函数,每个函数都能接收上一个函数输出的结果(函数为高阶函数),作为自己的输入,这样才能嵌套生成最终结果,同时,计算的顺序也是一定从里向外,所以换个写法可以写成:

start ---x--> f1(x) --(temp value1)--> f2(temp value1) --(temp value2)--> f3(temp value2) ---> result

里面f1(x),f2(x),f3(x)就可以看成,或者说是RAC中的RACStream对象

Stream就是一个 按时间排序的Events(Ongoing events ordered in time)序列

函数式编程的特点?

  1. 闭包&高阶函数
  2. 惰性计算
  3. 不改变状态
  4. 递归

高阶函数:函数可以做返回值或参数
惰性计算:也叫延迟求值,意味着对象在需要时求值,而不是在创建时求值。(类似懒加载)

通过高阶函数以点为连接将多个函数连接在一起完成参数传递和复杂的操作!

函数式(链式)编程举例:
例如在Masonry中的这样的代码
make.right.equalTo(self.right).insets(kPadding);

链式编程

将多个操作(多行代码)通过点号(.)链接在一起成为一句代码,使代码可读性好a(1).b(2).c(3)。
链式编程特点:方法的返回值是block,block必须有返回值(本身对象),block参数(需要操作的值)

代表:masonry框架

响应式编程

响应式编程思想:不需要考虑调用顺序,只需要知道考虑结果,类似于蝴蝶效应,产生一个事件,会影响很多东西,这些事件像流一样的传播出去,然后影响结果,借用面向对象的一句话,万物皆是流。

举个例子,如果你要求 c = a + b; 那么你必须得知道 a 跟 b 的值,先定义 a 跟 b,并给他们赋值,这是正常的思路。
但是响应式编程的话,你可以先定义 a 跟 b 的值,然后让 c = a + b,在后面给 a 跟 b 赋值,把 a 跟 b 与 c 绑定了,赋值后 c 的值也会相应的改变。
这就是响应式编程思想,经过上面一个小例子,相比大家应该能理解上面那句不需要考虑调用顺序,只需要考虑结果这句话的意思了吧。

代表:KVO运用


RAC主要包括的核心组件:

RACSteam 
RACSequence RACSignal
RACSubscriber
RACDisposable
RACScheduler
Cocoa框架适配工具

RACStream

RACStream是一个抽象类,是不能直接实例化的;
RACStream作为一个描述抽象的父类,方法由具体子类来实现;
RACStream的两个子类分别是RACSignalRACSequence

RACSignal

RACSignal

“A signal, represented by the RACSignal class, is a push-driven stream.”

也就是,RACSignal是一个push-driven stream

RACSignal有很多方法,你可以用来去订阅这些不同的事件类型。每一个方法接收一个或多个block,当事件发生时,block中的逻辑会被执行。在这种情况下,您可以看到subscribeNext:方法用于提供在每个next事件上执行的块。

ReactiveCocoa框架使用了categories去给很多标准的 UIKit 组件增加signals,这样你可以给它们增加订阅,这就是这个文本域的rac_textSignal域的来源。

比如

[self.usernameTextField.rac_textSignal subscribeNext:^(id x) {
	NSLog(@"%@", x);
}];

rac_textSignal就是一个UITextField的分类

在这里插入图片描述
ReactiveCocoa指导一笔记这篇文章中,作者给我们举了一个监听UITextField的小例子,很是经典。
在下面的代码例子中:

[[[self.usernameTextField.rac_textSignal
  map:^id(NSString *text) {
    return @(text.length);
  }]
  filter:^BOOL(NSNumber *length) {
    return [length integerValue] > 3;
  }]
  subscribeNext:^(id x) {
    NSLog(@"%@", x);
  }];

map接收了NSString输入,并且获取了它的长度,用一个NSNumber返回出去。

其工作原理的图像:
在这里插入图片描述

RAC宏

在这里插入图片描述

这个RAC宏允许你分配一个signal的输出给一个对象的属性。
它使用了两个参数,第一个是那个对象,包含着将要设置的属性,第二个是属性名。
每一次signal发射一个next事件,被传递的值就被分配给给定的属性。

第一个参数self.usernameTextField与第二个参数backgroundColor绑定
将block内部的return结果,赋值给第二个参数

这里你可以看到两个简单的管道,接收文本signal,把他们map成指示有效的布尔型,然后再map成一个 UIColor,和文本域的background的color属性绑定起来
在这里插入图片描述

Combining signals

当前的代码已经有了signal发射了一个布尔值去指示着username和password域是否有效:validUsernameSignalvalidPasswordSignal
你的任务是合并这两个signal去决定什么时候可以让这个按钮开始工作。

    RACSignal *signupActiveSignal = [RACSignal combineLatest:@[validUsernameSignal, validPasswordSignal] reduce:^id(NSNumber *usernameValid, NSNumber *passwordValid){
        //@()是一个NSNumber,其内部是bool类型
        return @([usernameValid boolValue] && [passwordValid boolValue]);
    }];
    
    [signupActiveSignal subscribeNext:^(NSNumber *signupActive) {
        self.signInButton.enabled = [signupActive boolValue];
    }];

上面的代码使用了combineLatest:reduce:方法去合并由validUsernameSignal和validPasswordSignal发射的最后的值到一个新的signal。
每一次这两个源signal中的一个发射出一个新值,这个reduce block就执行,并且它返回的值被作为combined signal的next的值。

在这里插入图片描述

上面描绘了两个重要的概念:

  • 分割 – signal可以有多个订阅者,可以作为后续管道步骤的源。在上面的图中,注意那个布尔型signal指示这password和 username的有效性被分割并且被用于不同的目的。
  • 合并 – 多个signal可以合并去创建一个新的signal。在这个例子中,两个布尔型的signal被合并。然后,你可以合并发生任何值类型的signal。

在这里插入图片描述
在这里插入图片描述
上面的方法创建了一个signal,发射于当前的用户名和密码。
上面的代码使用了RACSignal的createSignal:,为了来创建signal。
描述了这个signal的block是一个参数,传递给这个方法。当这个signal有一个订阅者,这个block中的代码将会被执行。

这个block被传递了一个subscriber实例,实现了RACSubscriber协议,这个协议拥有一些方法,使得你可以调用去发射event;你也可以发射任意数量的next事件,最后终止于一个error或者complete事件。在这个案例中,它发送了一个next事件,指明了sign-in是否成功,跟随者一个complete事件。

可以这么理解,使用[RACSignal createSignal:]方法,block内部可以都写上:
[subscriber sendNext:xxx];
[subscriber sendCompleted];
return nil;

这个block的返回类型是一个RACDisposable对象,它允许你去执行一些清理工作 – 当一个subscription(订阅)被取消或者被丢弃时需要进行的。
这里的这个signal没有清理的需求,所以返回了一个nil。

在这里插入图片描述
在这里插入图片描述

参考ReactiveCocoa指导一笔记

RACSignal和RACSequence

RACSignal 是基于时间的数据流
RACSequence是不基于时间的数据流

push-driver和pull-driver

怎么理解push-driver和pull-driver呢?

push-driver是任何时刻有数据了都会push给调用者,如果你没处理就丢失了。
pull-driver是任何时刻我有数据了你都可以获取到,因为数据先存储了,取数据的时间控制在调用者上。

更多参考学习: iOS 开源库源码分析之 ReactiveCocoa

Push-driver:被动获取,类比看电视,你不看就没了
Pull-driver:主动获取,类比读书,你想读多少读多少

Push-driver可以类比看电视,节目不管你看不看,都一直播放,你错过了就是错过了。
Pull-driver可以类比看书,知识和文字不管你看不看,一直都在书里。

RACSignal -> Push-driver
RACSequence -> Pull-driver

冷信号和热信号

RACSignal有休眠(cold)和激活(hot)两种状态,也就是所谓的冷信号和热信号;
一般情况下,一个RACSignal创建之后都处于cold状态,有人去subscribe才被激活。

Event

RACSignal能产生且只能产生三种事件:next、completed,error

  • next 表示这个 Signal 产生了一个值(成功生产了一块巧克力)
  • completed 表示 Signal 结束,结束信号只标志成功结束,不带值(一个批次的订单完成了)
  • error 表示 Signal 中出现错误,立刻结束(一个机器坏了,生产线立刻停止运转)

Subjects

一个Subject,在RAC中代表的是RACSubject类,是一种可以被手动控制的信号。Subject可以认为是可变的信号,就像NSMutableArray对于NSArray一样。Subject是很有用的连接非RAC代码到RAC的很有用的工具。

Sequences

sequence,在RAC中代表的是RACSequence类,是一种pull-driven的流。Sequence是一种集合类型,类似NSArray.

RACSubscriber

RACSubscriber是订阅者,所有实现了 RACSubscriber 协议的类都可以作为信号源的订阅者。一次订阅是通过调用-subscribeNext:error:completed产生的。订阅会持有它的signal对象,并且在信号completed或者error的时候释放

RACScheduler

调度器,是一个串行的信号执行队列,用来执行任务或者传递结果。Schedulers类似于GCD中的队列,但是scheduler支持取消队列(通过disposables),并总是串行执行的

RACDisposable

清洁工,Disposables常常用来取消对一个信号的订阅。

Commands

command,在RAC中表示的是RACCommand类,可以创建或者订阅一个信号用来响应某些action动作。这可以很方便的来处理App中的用户交互。
在这里插入图片描述


3. 怎么使用RAC?

在项目中,使用Cocoapods管理,直接pod 'ReactiveObjC', '~>3.1.1'即可

值可以分为三种:普通值、元组、信号

万物皆信号📶
1 创建信号
RACSubject *subject = [[RACSubject alloc] init];
2 订阅信号

[subject subscribeNext:^(id  _Nullable x) {
    NSLog(@"%@", x);
}];

3 发送信号
[subject sendNext:@"哈哈"];

RAC的操作大概分为以下几种:

  1. 单个信号的变换
  2. 多个信号的组合
  3. 高阶操作
单个信号的变换:

单个信号的变换其中,
对值的操作:
map

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

筛选,筛选length > 2的元素

在这里插入图片描述
在这里插入图片描述

增加操作:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
多个信号的组合:

在这里插入图片描述
如果signalA有错误,则signalC也错误结束
如果signalB有错误,则signalC也错误结束

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


信号高阶操作

普通信号 变为 高阶 信号:
在这里插入图片描述

if/then/else就是三目运算

RAC使用举例:

使用RAC,代替KVO

RAC用法:
导入头文件#import <NSObject+RACKVOWrapper.h>

在这里插入图片描述
或者使用以下方法:
#import <ReactiveObjC.h>
在这里插入图片描述

UIButton的addTarget方法也可以使用RAC:

在这里插入图片描述

通知
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(NSNotification * _Nullable x) {
        NSLog(@"---");
}];
textFiled监听
[_textFiled.rac_textSignal subscribeNext:^(NSString * _Nullable x){
	NSLog(@"%@", x);
}];

冷信号与热信号

什么是冷信号与热信号?

冷信号类似于 看点播 电脑上看电影(可以随时从头开始)
热信号类似于 看直播 电影院看电影(播到哪看哪)

Signal vs Subject

RACSubject是RACSignal的子类
RACSignal可以用的,RACSubject都可以用

RACSignal属于 冷信号
RACSubject属于 热信号

RACReplaySubject 带快速回播的Subject

冷信号 -> 热信号

冷信号可以变为热信号

RAC并发编程

RACScheduler
在这里插入图片描述

  • Scheduler底层使用GCD来实现
  • 可以“取消”(伪取消)
  • 与RAC其他组件高度整合
  • 一个Scheduler保证串行执行
  • 一个Scheduler的任务不保证线程是同一个
Scheduler的使用

在这里插入图片描述
在这里插入图片描述

问:下列代码的执行顺序?

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4. 使用RAC需要注意什么?

RAC 在应用中大量使用了 block,由于 Objective-C 语言的内存管理是基于引用计数的,为了避免循环引用问题,在block中如果要引用self,需要使用@weakify(self)@strongify(self)来避免强引用。

另外,在使用时应该注意 block 的嵌套层数,不恰当的滥用多层嵌套 block 可能给程序的可维护性带来灾难。


参考资料:
ReactiveCocoa - iOS 开发的新框架
Reactive Cocoa Tutorial [2] = 百变RACStream
RAC资源帖
ReactiveCocoa指导一笔记

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值