什么是 RAC ?
FRP 函数式响应式编程
信号:事件发生 -> 信号传递 -> 事件响应
🌰
点外卖 -> 餐厅收到订单 -> 做饭 -> 送外卖 -> 吃,用户只需关心结果能否吃到外卖,并不需要在意过程
如果中途没有外卖员接单等,那么整个事件流就会产生error,就会停止,用户就会接到退单通知
送单完成,会发送 订单完成 的通知
一般的模式,可能我们会在这个过程中去检查每一步可能产生的error,比如
1.外卖员需要从众多外卖中定位自己的单子(代码内聚是否够高?)target - action
2.厨师在不在厨房,厨师做的菜是否符合要求等 delegate
更加注重过程及实现,但操作费时费力,rac 可以让我们更加针对业务
Monad:
Monad 是一种设计模式,表示将一个运算过程,通过函数拆解成互相连接的多个步骤
你只要提供下一步运算所需的函数,整个运算就会自动进行下去
RACSignal
1.RACSignal 是继承于 RACStream 的
RACStream拥有很多抽象方法,bind,return 等
父类不提供实现,供子类实现
- return 在 RACSignal 的实现是:
// RACSignal.m
// 讲一个 Objc 对象 转化为 信号,建立连接
+ (RACSignal *)return:(id)value {
return [RACReturnSignal return:value];
}
其中 RACReturnSignal 只是将 value 进行包装
+ (RACSignal *)return:(id)value {
RACReturnSignal *signal = [[self alloc] init];
signal->_value = value;
return signal;
}
什么是 热信号 和 冷信号?
RAC 的世界里是区分 冷热信号的,那么问题来了
1.区别是什么?
2.为什么要区分?
1.区别是什么?
冷信号
冷信号是一种只有被订阅了,才会执行的信号;简单来说,用户需要执行订阅这个动作,对其进行订阅,才可以产生事件流。
🌰
报刊杂志的订阅
1.如果用户不主动选择订阅,就无法浏览其内容
2.不论是谁订阅,报刊的同一期都会根据 用户指定时间 送达
3.报刊人手一份,互不干扰,一对一
如果用户不产生订阅行为,那么 用户 和 报刊之间可以说毫无关系
代码:
RACSignal *cold = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"start");
[subscriber sendNext:@"123"];
[subscriber sendCompleted];
return nil;
}];
[cold subscribeNext:^(id _Nullable x) {
NSLog(@"subscribe 1 ---- %@",x);
}];
/// 用户需要1小时后送达
[[RACScheduler mainThreadScheduler] afterDelay:1.0 schedule:^{
[cold subscribeNext:^(id _Nullable x) {
NSLog(@"subscribe 2 ---- %@",x);
}];
}];
NSLog(@"done");
分析:
冷信号是一对一的, sub2 的订阅时机 并不会影响 信号的重新发送, 订阅了2次就发送2次
热信号
🌰
电视直播
1.用户打开直播,之前播过的东西无法后退,状态是实时的
2.所有人可以观看同一个直播,事件的产生是可以 一对多的
3.状态共享,直播开始的时候,打开电视的用户可以同时观看;直播结束的时候,都会结束
RACSignal *cold = [[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"start");
[subscriber sendNext:@"123"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"第二次发送"];
[subscriber sendCompleted];
});
return nil;
}] multicast: [RACSubject subject]] autoconnect];
[cold subscribeNext:^(id _Nullable x) {
NSLog(@"subscribe 1 ---- %@",x);
}];
[[RACScheduler mainThreadScheduler] afterDelay:1.0 schedule:^{
[cold subscribeNext:^(id _Nullable x) {
NSLog(@"subscribe 2 ---- %@",x);
}];
}];
NSLog(@"done");
分析
1.热信号是有状态,一对多的
- multicast: [RACSubject subject]] autoconnect 将 cold 转为 hot ,并且 autoconnect 只允许 connect 一次
- 第二次的发送,可以看到 sub1 和 sub2 都 同时 接收到了信号
⚠️ 热信号的订阅,必须在发送之前,相当于看直播得先打开电视机
❗️ 本质区别:
热信号是可以具有保持状态的能力的,虽然是直播,但是某些 subject 的子类也可以把直播录下来,当作录播发送
冷信号是没有状态的,仅此当前的一次订阅与发送
2.为什么要区分?
因为冷信号具有副作用
RACSignal *fetchData = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"网络请求开始");
[subscriber sendNext:@{@"title": @"lt",
@"des": @"cool"
}];
[subscriber sendCompleted];
return nil;
}];
RACSignal *titleSignal = [fetchData flattenMap:^__kindof RACSignal * _Nullable(NSDictionary *dic) {
return [RACSignal return:dic[@"title"]];
}];
RACSignal *desSignal = [fetchData flattenMap:^__kindof RACSignal * _Nullable(NSDictionary *dic) {
return [RACSignal return:dic[@"des"]];
}];
RAC(self.statusLabel, text) = titleSignal;
RAC(self.textF, text) = desSignal;
// print
// 网络请求开始
// 网络请求开始
2次网络请求意味着 fetchData 的订阅执行了2次,原因在于 flattenMap 内部产生了新的信号
❗️ 任何的信号转换即是对原有的信号进行订阅从而产生新的信号
flattenMap:
// RACStream
- (__kindof RACStream *)flattenMap:(__kindof RACStream * (^)(id value))block {
Class class = self.class;
return [[self bind:^{
return ^(id value, BOOL *stop) {
id stream = block(value) ?: [class empty];
return stream;
};
}] setNameWithFormat:@"[%@] -flattenMap:", self.name];
}
Bind 原理:
Bind 作为 RAC 的核心,贯穿了 RAC 的很多方法内核
调用bind,其中传入的 block 类型如下
/// A block which accepts a value from a RACSignal and returns a new signal.
/// 从 RACSignal 中接收 Value,并且返回一个新的信号
///
/// Setting `stop` to `YES` will cause the bind to terminate after the returned
/// value. Returning `nil` will result in immediate termination.
typedef RACSignal *(^RACSignalBindBlock)(ValueType value, BOOL *stop);
bind 核心代码
- (RACSignal *)bind:(RACSignalBindBlock (^)(void))block {
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
RACSignalBindBlock bindingBlock = block();
return [self subscribeNext:^(id x) {
BOOL stop = NO;
[bindingBlock(x, &stop) subscribeNext:^(id x) {
[subscriber sendNext:x];
}];
}];
}] setNameWithFormat:@"[%@] -bind:", self.name];
}
// 整个过程
- 订阅源信号
- 原信号发出的信号 通过 bindBlock 转换
- 对 bindblock 转换的信号 进行订阅,并发送给原信号
// 结论
通过bind 方法 对原信号进行转化,并生成新的信号
如下图
副作用
所以,在上述冷信号的副作用中,2个 flattenMap 内部都对源信号 执行了相同的代码块,让网络请求无意之中执行了2次
为什么需要产生新的信号?
应该是因为源信号的操作不足以满足需求,我们需要源信号进行处理转换,对新的信号进行订阅
那么具体热信号指的是什么?
- RACSignal 去除 RACSubject 都是冷信号
RACSubject 是继承RACSignal 的,那么基本的原理相似
RACSubject 的特征:
RACSubject 是可变的
RACSubject 是 非RAC到RAC的一个桥梁
RACSubject 可以附加行为,例如
RACReplaySubject
具备为未来订阅者缓冲事件的能力
RACSubject 是有状态的,具备了热信号的特征,RACSubject 及其子类都属于热信号
为什么说RACSubject 是热信号 什么称之为状态?
先看一下 RACsubject 的源码:
/// 与 RACSignal 不同的是, subject 维护了一个数组 对所有订阅者进行保存
@property (nonatomic, strong, readonly) NSMutableArray *subscribers;
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSMutableArray *subscribers = self.subscribers;
@synchronized (subscribers) {
[subscribers addObject:subscriber];
}
return disposable;
}
/// 遍历执行 block
- (void)enumerateSubscribersUsingBlock:(void (^)(id<RACSubscriber> subscriber))block {
NSArray *subscribers;
@synchronized (self.subscribers) {
subscribers = [self.subscribers copy];
}
for (id<RACSubscriber> subscriber in subscribers) {
block(subscriber);
}
}
我所理解的状态就是,一个东西有状态意味着它不是即时的东西,它有保存事务的能力
RACSubject 内部有数组保存所有的订阅者,那么我们就可以拿到所有订阅的信息
可以拿到订阅者
可以拿到信号值
可以自己决定怎么发送
这也是解决副作用的关键点
so 怎么获取热信号?
通过对冷信号的订阅,产生新的 RACSubject ,再对 RACSubject 进行订阅
RAC 给出如下几个方法:
- (RACMulticastConnection *)publish;
- (RACMulticastConnection *)multicast:(RACSubject *)subject;
- (RACSignal *)replay;
- (RACSignal *)replayLast;
- (RACSignal *)replayLazily;
实现原理:
Publish
内部也是 调用了 multicast,所以我们只需要看一下 multicast
/// 只是自己帮我们创建 RACSubject
- (RACMulticastConnection *)publish {
RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name];
RACMulticastConnection *connection = [self multicast:subject];
return connection;
}
- (instancetype)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject {
NSCParameterAssert(source != nil);
NSCParameterAssert(subject != nil);
self = [super init];
/// 源信号
_sourceSignal = source;
_serialDisposable = [[RACSerialDisposable alloc] init];
/// 热信号
_signal = subject;
return self;
}
⚠️ 我们在使用 RACMulticastConnection的时候,其实在RACSubject对源信号进行订阅
- cold signla 发送值,保存与 hotSignal中
- 后续我们的操作其实都是对 hotSignal 的状态进行 操作
- 所以cold block 只会执行一次
replay 热信号
block 只会执行一次
/// 内部使用了,RACReplaySubject, 可以发送所有保存的信号值
- (void)replay {
RACSignal *replay = [[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"begin");
[subscriber sendNext:@"1"];
[subscriber sendNext:@"2"];
return nil;
}] replay];
// print 1, 2
}
⚠️ 就算没有 订阅者,block 也会执行,因为它需要将发送的信号值 1 和 2,先保存起来
如果我们想重复利用某个信号发出的所有值,可以避免重复的请求
replayLast 热信号
block 只会执行一次
只发送最后一个保存的值
replayLazily
❗️ 类似懒加载
与 replay不同的是: 没有订阅者 不执行默认 block ,也就是只有当源信号被订阅的时候,内部才会订阅
也就是说无论多少个订阅者,block 内部的代码只会在第一次执行
有且只有一次
有订阅者才可以发送,冷信号
- (RACSignal *)replayLazily {
RACMulticastConnection *connection = [self multicast:[RACReplaySubject subject]];
return [[RACSignal
defer:^{
[connection connect];
return connection.signal;
}]
setNameWithFormat:@"[%@] -replayLazily", self.name];
}
+ (RACSignal *)defer:(RACSignal<id> * (^)(void))block {
NSCParameterAssert(block != NULL);
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
return [block() subscribe:subscriber];
}] setNameWithFormat:@"+defer:"];
}
解答 为什么保持状态的热信号可以避免副作用?
因为我们可以根据状态来避免副作用的产生,也就是多次频繁的调用 block
replay replaylast replayLazily 都只会调用一次 block内部代码
而 replayLazily 不会默认执行 block,只有当有订阅者才会执行
所以上述的副作用解法:
RACSignal *fetchData = [[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"网络请求开始");
[subscriber sendNext:@{@"title": @"lt",
@"des": @"cool"
}];
[subscriber sendCompleted];
return nil;
}]replayLazily] ;
/// 添加 replayLazily 即可 , replay 和 replayLast 也可以避免重复执行
/// 但是我们想在有订阅者产生的时候,才去网络请求,避免资源无用消耗
RACSignal *titleSignal = [fetchData flattenMap:^__kindof RACSignal * _Nullable(NSDictionary *dic) {
return [RACSignal return:dic[@"title"]];
}];
RACSignal *desSignal = [fetchData flattenMap:^__kindof RACSignal * _Nullable(NSDictionary *dic) {
return [RACSignal return:dic[@"des"]];
}];
RAC(self.statusLabel, text) = titleSignal;
RAC(self.textF, text) = desSignal;
以上基本上就是 冷热信号的概念
RACCommond
并非继承于 RACStream 或者 RACSignal,而是一个管理 RACSignal 的类,也就是一个对象
- executionSignals 信号的信号
- executing 是否正在执行,replayLast 捕获最新状态
- enabled 是否可以再次被执行
- errors
适用于网络请求 或者 按钮点击
RACCommand *cmd = [[RACCommand alloc]initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
// 发送请求
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSString *str = input;
[subscriber sendNext:str];
[subscriber sendCompleted];
return nil;
}];
}];
// 方法1
[[cmd.executionSignals switchToLatest]subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
// 方法2
[cmd.executionSignals subscribeNext:^(RACSignal *x) {
[x subscribeNext:^(id _Nullable a) {
NSLog(@"sub - %@", a);
}];
}];
// 执行
[cmd execute:@"1"];
Throttle 节流
多用于搜索
频繁,不必要的请求必定会浪费用户流量
[[[_textF.rac_textSignal skip:1] throttle: 1] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"输入的字符是 --- %@", x);
}];