RAC(ReactiveCocoa)
文章目录
- RAC(ReactiveCocoa)
- 响应式编程和函数式编程的区别
- 函数式编程
- 响应式编程
- 响应式编程的优点
- RAC操作
- 1.利用button点击实现点击事件和传值
- 2.RACSignal用法
- RACSignal总结:
- 3.对于label的TapGesture和KVO测试
- 4.对textField的监听即过滤操作
- 5.RAC过滤操作
- 6.RAC映射操作
- 7.RAC组合操作
- 8.RAC定时器实现倒计时
- RAC的源码分析
- 1.信号产生
- 2.订阅信号
- 3.发送信号
- 4. 销毁
ReactiveCocoa(简称为RAC),响应式框架,是由Github开源的一个应用于iOS和OS开发的新框架,Cocoa是苹果整套框架的简称,因此很多苹果框架喜欢以Cocoa结尾。
在我们iOS开发过程中,经常会响应某些事件来处理某些业务逻辑,例如按钮的点击,上下拉刷新,网络请求,属性的变化(通过KVO)或者用户位置的变化(通过CoreLocation)。但是这些事件都用不同的方式来处理,比如action、delegate、KVO、callback(回调)等。
其实这些事件,都可以通过RAC处理,ReactiveCocoa为事件提供了很多处理方法,而且利用RAC处理事件很方便,可以把要处理的事情,和监听的事情的代码放在一起,这样非常方便我们管理,就不需要跳到对应的方法里。非常符合我们开发中高聚合,低耦合的思想。
响应式编程和函数式编程的区别
函数式编程
1.如果想再去调用别的方法,那么就需要返回一个对象;
2.如果想用()去执行,那么需要返回一个block;
3.如果想让返回的block再调用对象的方法,那么这个block就需要返回一个对象(即返回值为一个对象的block)。
4.高阶函数:在函数式编程中,把函数当参数来回传递,而这个,说成术语,我们把他叫做高阶函数。在oc中,blocks是被广泛使用的参数传递,它实际上是匿名函数。
响应式编程
1.响应式编程是一种和事件流有关的编程模式,关注导致状态值改变的行为事件,一系列事件组成了事件流。
2.一系列事件是导致属性值发生变化的原因。FRP非常类似于设计模式里的观察者模式。
3.FRP与普通的函数式编程相似,但是每个函数可以接收一个输入值的流,如果其中,一个新的输入值到达的话,这个函数将根据最新的输入值重新计算,并且产生一个新的输出。这是一种”数据流”编程模式。
响应式编程的优点
1) 开发过程中,状态以及状态之间依赖过多,RAC更加有效率地处理事件流,而无需显式去管理状态。在OO或者过程式编程中,状态变化是最难跟踪,最头痛的事。这个也是最重要的一点。
2) 减少变量的使用,由于它跟踪状态和值的变化,因此不需要再申明变量不断地观察状态和更新值。
3) 提供统一的消息传递机制,将oc中的通知,action,KVO以及其它所有UIControl事件的变化都进行监控,当变化发生时,就会传递事件和值。
4) 当值随着事件变换时,可以使用map,filter,reduce等函数便利地对值进行变换操作。
RAC操作
1.利用button点击实现点击事件和传值
// MainViewController.m
- (void)addButton { //button点击和传值
self.firstButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
self.firstButton.frame = CGRectMake(0, 0, 100, 100);
[self.firstButton setTitle:@"未获" forState:UIControlStateNormal];
self.firstButton.backgroundColor = [UIColor purpleColor];
[self.view addSubview:self.firstButton];
[[self.firstButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
SecondViewController *secondViewController = [[SecondViewController alloc] init];
secondViewController.modalPresentationStyle = UIModalPresentationFullScreen;
secondViewController.subject = [RACSubject subject];
[secondViewController.subject subscribeNext:^(id x) {
NSLog(@"%@",x);
[self.firstButton setTitle:x forState:UIControlStateNormal];
}];
[self presentViewController:secondViewController animated:YES completion:nil];
}];
}
// SecondViewController.m
self.backButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
self.backButton.backgroundColor = [UIColor redColor];
self.backButton.frame = CGRectMake(0, 0, 100, 100);
[self.backButton setTitle:@"返回" forState:UIControlStateNormal];
[self.view addSubview:self.backButton];
[[self.backButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
[self.subject sendNext:@"已获"];
[self dismissViewControllerAnimated:YES completion:nil];
}];![请添加图片描述](https://img-blog.csdnimg.cn/d4292465d216430497d5920fa5dc6342.png)
效果展示:
2.RACSignal用法
// 1.创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 3.发送信号
[subscriber sendNext:@"牛逼"];
[subscriber sendNext:@"牛逼2"];
// 4.取消信号,如果信号想要被取消,就必须返回一个RACDisposable
// 信号什么时候被取消:1.自动取消,当一个信号的订阅者被销毁的时候机会自动取消订阅,2.手动取消,
//block什么时候调用:一旦一个信号被取消订阅就会调用
//block作用:当信号被取消时用于清空一些资源
return [RACDisposable disposableWithBlock:^{
NSLog(@"取消订阅");
}];
}];
// 2. 订阅信号
// subscribeNext
// 把nextBlock保存到订阅者里面
// 只要订阅信号就会返回一个取消订阅信号的类
RACDisposable *disposable = [signal subscribeNext:^(id x) {
// block的调用时刻:只要信号内部发出数据就会调用这个block
NSLog(@"======%@", x);
}];
// 取消订阅
[disposable dispose];
输出:
RACSignal总结:
一.核心:
1.核心:信号类
2.信号类的作用:只要有数据改变就会把数据包装成信号传递出去
3.只要有数据改变就会有信号发出
4.数据发出,并不是信号类发出,信号类不能发送数据
二.使用方法:
1.创建信号
2.订阅信号
三.实现思路:
1.当一个信号被订阅,创建订阅者,并把nextBlock保存到订阅者里面。
2.创建的时候会返回 [RACDynamicSignal createSignal:didSubscribe];
3.调用RACDynamicSignal的didSubscribe
4.发送信号[subscriber sendNext:value];
5.拿到订阅者的nextBlock调用
3.对于label的TapGesture和KVO测试
- (void)addKVOandTapGesture { //对于label的TapGesture和KVO测试
self.firstLabel = [[UILabel alloc] init];
self.firstLabel.text = @"未点击";
self.firstLabel.frame = CGRectMake(0, 100, 100, 100);
[self.view addSubview:self.firstLabel];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] init];
self.firstLabel.userInteractionEnabled = YES;
[self.firstLabel addGestureRecognizer:tap];
[tap.rac_gestureSignal subscribeNext:^(__kindof UIGestureRecognizer * _Nullable x) {
self.firstLabel.text = [NSString stringWithFormat:@"%d", arc4random()];
}];
//KVO
[RACObserve(self, self.firstLabel.text) subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
}];
[[self.firstLabel.text rac_valuesAndChangesForKeyPath:@"text" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
}
4.对textField的监听即过滤操作
self.firstTextField = [[UITextField alloc] init];
self.firstTextField.backgroundColor = [UIColor orangeColor];
[self.view addSubview:self.firstTextField];
self.firstTextField.frame = CGRectMake(100, 100, 200, 30);
[self.firstTextField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
NSLog(@"%@", x);
}];
// 只有当文本框的内容长度大于5,才获取文本框里的内容
[[self.firstTextField.rac_textSignal filter:^BOOL(id value) {
// value 源信号的内容
return [value length] > 5;
// 返回值 就是过滤条件。只有满足这个条件才能获取到内容
}] subscribeNext:^(id x) {
NSLog(@"已过滤-----%@", x);
}];
输出:
5.RAC过滤操作
#pragma mark RACSubject过滤
- (void)skipRACSubject {
// 跳跃 : 如下,skip传入2 跳过前面两个值
// 实际用处: 在实际开发中比如 后台返回的数据前面几个没用,我们想跳跃过去,便可以用skip
RACSubject *subject = [RACSubject subject];
[[subject skip:2] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@3];
}
- (void)distinctUntilChangedRACSubject {
//distinctUntilChanged:-- 如果当前的值跟上一次的值一样,就不会被订阅到
RACSubject *subject = [RACSubject subject];
[[subject distinctUntilChanged] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
// 发送信号
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@2]; // 不会被订阅
}
- (void)takeRACSubject {
// take:可以屏蔽一些值,去前面几个值---这里take为2 则只拿到前两个值
RACSubject *subject = [RACSubject subject];
[[subject take:2] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
// 发送信号
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@3];
}
- (void)takeLastRACSubject {
//takeLast:和take的用法一样,不过他取的是最后的几个值,如下,则取的是最后两个值
//注意点:takeLast 一定要调用sendCompleted,告诉他发送完成了,这样才能取到最后的几个值
RACSubject *subject = [RACSubject subject];
[[subject takeLast:2] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
// 发送信号
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@3];
[subject sendCompleted];
}
- (void)ignoreRACSubject {
//ignore:忽略一些值
//ignoreValues:表示忽略所有的值
// 1.创建信号
RACSubject *subject = [RACSubject subject];
// 2.忽略一些值
RACSignal *ignoreSignal = [subject ignore:@2]; // ignoreValues:表示忽略所有的值
// 3.订阅信号
[ignoreSignal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
// 4.发送数据
[subject sendNext:@2];
}
6.RAC映射操作
- (void)map {
// 创建信号
RACSubject *subject = [RACSubject subject];
// 绑定信号
RACSignal *bindSignal = [subject map:^id(id value) {
// 返回的类型就是你需要映射的值
return [NSString stringWithFormat:@"ws:%@", value]; //这里将源信号发送的“123” 前面拼接了ws:
}];
// 订阅绑定信号
[bindSignal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
// 发送信号
[subject sendNext:@"123"];
}
- (void)flatMap {
// 创建信号
RACSubject *subject = [RACSubject subject];
// 绑定信号
RACSignal *bindSignal = [subject flattenMap:^RACStream *(id value) {
// block:只要源信号发送内容就会调用
// value: 就是源信号发送的内容
// 返回信号用来包装成修改内容的值
return [RACReturnSignal return:value];
}];
// flattenMap中返回的是什么信号,订阅的就是什么信号(那么,x的值等于value的值,如果我们操纵value的值那么x也会随之而变)
// 订阅信号
[bindSignal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
// 发送数据
[subject sendNext:@"123"];
}
- (void)flattenMap2 {
// flattenMap 主要用于信号中的信号
// 创建信号
RACSubject *signalofSignals = [RACSubject subject];
RACSubject *signal = [RACSubject subject];
// 订阅信号
//方式1
// [signalofSignals subscribeNext:^(id x) {
//
// [x subscribeNext:^(id x) {
// NSLog(@"%@", x);
// }];
// }];
// 方式2
// [signalofSignals.switchToLatest ];
// 方式3
// RACSignal *bignSignal = [signalofSignals flattenMap:^RACStream *(id value) {
//
// //value:就是源信号发送内容
// return value;
// }];
// [bignSignal subscribeNext:^(id x) {
// NSLog(@"%@", x);
// }];
// 方式4--------也是开发中常用的
[[signalofSignals flattenMap:^RACStream *(id value) {
return value;
}] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
// 发送信号
[signalofSignals sendNext:signal];
[signal sendNext:@"123"];
}
7.RAC组合操作
#pragma mark 组合
// 把多个信号聚合成你想要的信号,使用场景----:比如-当多个输入框都有值的时候按钮才可点击。
// 思路--- 就是把输入框输入值的信号都聚合成按钮是否能点击的信号。
- (void)combineLatest {
RACSignal *combinSignal = [RACSignal combineLatest:@[self.firstTextField.rac_textSignal, self.secondTextField.rac_textSignal] reduce:^id(NSString *account, NSString *pwd){ //reduce里的参数一定要和combineLatest数组里的一一对应。
// block: 只要源信号发送内容,就会调用,组合成一个新值。
NSLog(@"%@ %@", account, pwd);
return @(account.length && pwd.length);
}];
// // 订阅信号
// [combinSignal subscribeNext:^(id x) {
// self.combinationButton.enabled = [x boolValue];
// }]; // ----这样写有些麻烦,可以直接用RAC宏
RAC(self.combinationButton, enabled) = combinSignal;
}
- (void)zipWith {
//zipWith:把两个信号压缩成一个信号,只有当两个信号同时发出信号内容时,并且把两个信号的内容合并成一个元祖,才会触发压缩流的next事件。
// 创建信号A
RACSubject *signalA = [RACSubject subject];
// 创建信号B
RACSubject *signalB = [RACSubject subject];
// 压缩成一个信号
// **-zipWith-**: 当一个界面多个请求的时候,要等所有请求完成才更新UI
// 等所有信号都发送内容的时候才会调用
RACSignal *zipSignal = [signalA zipWith:signalB];
[zipSignal subscribeNext:^(id x) {
NSLog(@"%@", x); //所有的值都被包装成了元组
}];
// 发送信号 交互顺序,元组内元素的顺序不会变,跟发送的顺序无关,而是跟压缩的顺序有关[signalA zipWith:signalB]---先是A后是B
[signalA sendNext:@1];
[signalB sendNext:@2];
}
// 任何一个信号请求完成都会被订阅到
// merge:多个信号合并成一个信号,任何一个信号有新值就会调用
- (void)merge {
// 创建信号A
RACSubject *signalA = [RACSubject subject];
// 创建信号B
RACSubject *signalB = [RACSubject subject];
//组合信号
RACSignal *mergeSignal = [signalA merge:signalB];
// 订阅信号
[mergeSignal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
// 发送信号---交换位置则数据结果顺序也会交换
[signalB sendNext:@"下部分"];
[signalA sendNext:@"上部分"];
}
// then --- 使用需求:有两部分数据:想让上部分先进行网络请求但是过滤掉数据,然后进行下部分的,拿到下部分数据
- (void)then {
// 创建信号A
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 发送请求
NSLog(@"----发送上部分请求---afn");
[subscriber sendNext:@"上部分数据"];
[subscriber sendCompleted]; // 必须要调用sendCompleted方法!
return nil;
}];
// 创建信号B,
RACSignal *signalsB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 发送请求
NSLog(@"--发送下部分请求--afn");
[subscriber sendNext:@"下部分数据"];
return nil;
}];
// 创建组合信号
// then;忽略掉第一个信号的所有值
RACSignal *thenSignal = [signalA then:^RACSignal *{
// 返回的信号就是要组合的信号
return signalsB;
}];
// 订阅信号
[thenSignal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
}
// concat----- 使用需求:有两部分数据:想让上部分先执行,完了之后再让下部分执行(都可获取值)
- (void)concat {
// 组合
// 创建信号A
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 发送请求
// NSLog(@"----发送上部分请求---afn");
[subscriber sendNext:@"上部分数据"];
[subscriber sendCompleted]; // 必须要调用sendCompleted方法!
return nil;
}];
// 创建信号B,
RACSignal *signalsB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 发送请求
// NSLog(@"--发送下部分请求--afn");
[subscriber sendNext:@"下部分数据"];
return nil;
}];
// concat:按顺序去链接
//**-注意-**:concat,第一个信号必须要调用sendCompleted
// 创建组合信号
RACSignal *concatSignal = [signalA concat:signalsB];
// 订阅组合信号
[concatSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
}
输出:
8.RAC定时器实现倒计时
#pragma mark 定时器
- (void)countdown {
self.timeButton = [UIButton buttonWithType:UIButtonTypeCustom];
self.timeButton.frame = CGRectMake(200, 600, 100, 50);
self.timeButton.backgroundColor = [UIColor yellowColor];
[self.timeButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[self.timeButton setTitle:@"发送验证码" forState:UIControlStateNormal];
[self.view addSubview:self.timeButton];
[[self.timeButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
self.count = 10;
self.timeButton.enabled = NO;
[self.timeButton setTitle:[NSString stringWithFormat:@"%d", self.count] forState:UIControlStateNormal];
self.disposable = [[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]]subscribeNext:^(id x) {
if (self.count == 1) {
[self.timeButton setTitle:[NSString stringWithFormat:@"重新发送"] forState:UIControlStateNormal];
self.timeButton.enabled = YES;
[self.disposable dispose];
} else {
self.count--;
[self.timeButton setTitle:[NSString stringWithFormat:@"%d", self.count] forState:UIControlStateNormal];
NSLog(@"%d",self.count);
}
}];
}];
}
RAC的源码分析
1.信号产生
//1. RACSignal
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
//把block封装进来 利用多态原理返回 RACDynamicSignal
return [RACDynamicSignal createSignal:didSubscribe];//传入的参数就是一个block
}
//2.RACDynamicSignal
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
RACDynamicSignal *signal = [[self alloc] init];
// 绑定block,在订阅信号的时候调用
signal->_didSubscribe = [didSubscribe copy];
return [signal setNameWithFormat:@"+createSignal:"];
}
2.订阅信号
// 1.RACSignal
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);
//产生一个订阅者 绑定nextBlock
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
//这是self 是RACDynamicSignal 是本方法的调用者
return [self subscribe:o];
}
// 2.RACSubscriber
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCParameterAssert(subscriber != nil);
// 产生 RACCompoundDisposable : 核心销毁者
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
// RACPassthroughSubscriber 核心订阅者
/*
subscriber : 订阅者
signal : RACDynamicSignal 信号
disposable : 销毁者
传入者三个参数后 分别用三个属性保存住
三个属性保存住 是为了后续使用
*/
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
if (self.didSubscribe != NULL) {
// RACScheduler(封装了一个GCD)
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
//把核心订阅者传出去了, 这里的subscriber 就是create信号时block中的参数,订阅者
//执行创建信号时保存的didSubscribeBlock
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
//添加我的要销毁者的对象 把要销毁的对象都装进去 根据情况统一销毁
[disposable addDisposable:innerDisposable];
}];
[disposable addDisposable:schedulingDisposable];
}
return disposable;
}
- 订阅信号,产生了信号的一个订阅者,绑定nextBlock ,在下一步发送信号的时候调用这个nextBlock .
- 产生一个核心销毁者(RACCompoundDisposable),产生一个核心订阅者(RACPassthroughSubscriber),核心订阅者保存了销毁者,1中创建的订阅者,以及信号.执行didSubscribe(subscriber)
3.发送信号
- (void)sendNext:(id)value {
if (self.disposable.disposed) return;
if (RACSIGNAL_NEXT_ENABLED()) {
RACSIGNAL_NEXT(cleanedSignalDescription(self.signal), cleanedDTraceString(self.innerSubscriber.description), cleanedDTraceString([value description]));
}
//真正的订阅
[self.innerSubscriber sendNext:value];
}
//RACSubscriber
- (void)sendNext:(id)value {
//加锁
@synchronized (self) {
void (^nextBlock)(id) = [self.next copy];
if (nextBlock == nil) return;
nextBlock(value);
}
4. 销毁
在订阅信号的时候,产生了一个核心订阅者(RACPassthroughSubscriber)和一个核心销毁者(RACCompoundDisposable),
而核心订阅者内部持有了订阅者(subscriber),信号(signal),以及核心销毁者(disposable),
核心销毁者(disposable)可以被添加其他的销毁者,存入内部的一个数组中,在自身被释放的时候,会把数组中的也全部释放.
比如订阅者被self持有
[subscriber sendNext:@"123"];
self.subscriber = subscriber;
要想释放掉信号相关的内存,就必须self.subscriber = nil.
当订阅者被释放,在dealloc方法中
- (void)dealloc {
//核心销毁者开始执行dispose方法
[self.disposable dispose];
}
核心销毁者的dispose
- (void)dispose {
#if RACCompoundDisposableInlineCount
//C 数组 type * a[2]
RACDisposable *inlineCopy[RACCompoundDisposableInlineCount];
#endif
CFArrayRef remainingDisposables = NULL;
pthread_mutex_lock(&_mutex);
{
_disposed = YES;
#if RACCompoundDisposableInlineCount
for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
inlineCopy[i] = _inlineDisposables[i];
//当前的销毁数组,一个个的清理 数组移除
_inlineDisposables[i] = nil;
}
#endif
remainingDisposables = _disposables;
_disposables = NULL;
}
pthread_mutex_unlock(&_mutex);
#if RACCompoundDisposableInlineCount
// Dispose outside of the lock in case the compound disposable is used
// recursively.
for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
[inlineCopy[i] dispose];//存入的disposable各自调用各自的dispose方法
}
#endif
if (remainingDisposables == NULL) return;
CFIndex count = CFArrayGetCount(remainingDisposables);
CFArrayApplyFunction(remainingDisposables, CFRangeMake(0, count), &disposeEach, NULL);
CFRelease(remainingDisposables);
}
存进核心销毁者的各个disposable调用dispose方法
[RACDisposable dispose]
- (void)dispose {
void (^disposeBlock)(void) = NULL;
//遍历去找销毁对象
while (YES) {
void *blockPtr = _disposeBlock;
// OSAtomicCompareAndSwapPtrBarrier(v1,v2,v3)
/*
v1 与 v3 匹配 相同就返回yes
然后把v2 赋值给v3 也就是 = null
*/
if (OSAtomicCompareAndSwapPtrBarrier(blockPtr, NULL, &_disposeBlock)) {
if (blockPtr != (__bridge void *)self) {
disposeBlock = CFBridgingRelease(blockPtr);
}
break;
}
}
// 持有的block,成为了临时变量,执行完,block也就释放了
if (disposeBlock != nil) disposeBlock();
}
大佬博客
RACdemo地址