ReactiveCocoa(简称RAC)是由GitHub团队开源的一套基于Cocoa的并且具有FRP特性的框架。FRP(Functional Reactive Programming)即响应式编程。RAC就是一个第三方库,使用它可以大大简化代码,提高开发效率,目前公司也在范围使用。但疏于总结只是停留在会用的阶段,这次针对RAC做个全面认识和总结。
第一部分基础理论。
第二部分介绍一些常用类。
第三部分介绍一些常用语法。
知道各位猿码代码码的很辛苦,贴张图给大伙提提神,学习效率倍增~滴滴老司机开车了
一.基础理论
什么是信号?
RAC的核心就是信号,即RACSignal。信号可以看做是传递数据的工具,当数据变化时,信号就会发送改变的信息,以通知信号的订阅者执行方法。
什么是冷热信号?
1.Hot Observable是主动的,尽管你并没有订阅事件,但是它会时刻推送,就像鼠标移动;而Cold Observable是被动的,只有当你订阅的时候,它才会发布消息。
2.Hot Observable可以有多个订阅者,是一对多,集合可以与订阅者共享信息;而Cold Observable只能一对一,当有不同的订阅者,消息是重新完整发送。
3.RACSubject及其子类是热信号。RACSignal排除RACSubject类以外的是冷信号。
推荐美团的技术博客介绍详细的冷热信号,在这里也要感谢美团技术团队博客带来的帮助。
冷信号与热信号(一)
为什么要区分冷热信号(二)
怎么处理冷信号与热信号(三)
二.常用类
1.RACSignal
信号类,只有当数据变化时,才会发送数据,但是RACSignal自己不具备发送信号能力,而是交给订阅者去发送。默认一个信号发送数据完毕就会自动取消订阅,如果订阅者还在,就不会自动取消信号订阅,因此如果实际开发中需要自己控制订阅者的声明周期,可以stong持有,在特定的时机执行dispose方法取消订阅。
RACSignal订阅和发送信号一般过程如下:
1>创建信号 createSignal
RACSignal *single = [RACSignal createSignal:^RACDisposable *(id subscriber) {
}];
2>创建订阅者进行订阅
[single subscribeNext:]
3发送信号
[subscriber sendNext:]
RACSignal工作原理:
第一步 查看信号的创建过程:
当我们调用createSignal方法的时候,内部会调用子类RACDynamicSignal的createSignal方法创建一个信号single,并且在single中保存了block参数didSubscribe。
RACSignal.m:
+ ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe {
return [ RACDynamicSignal createSignal :didSubscribe];
}
RACDynamicSignal.m
+ ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe {
RACDynamicSignal *signal = [[ self alloc ] init ];
signal-> _didSubscribe = [didSubscribe copy ];
return [signal setNameWithFormat : @"+createSignal:" ];
}
第二步 查看信号订阅的过程:
当我们调用信号的subscribeNext方法,内部创建一个订阅者subscriber,并且会保存参数nextBlock,还有errorBlock、completedBlock。接下来会调用RACDynamicSignal的subscribe方法,之前保存的didSubscribe,因为RACDynamicSignal是RACSignal的子类,所以会执行到这里。
RACSignal.m:
- ( RACDisposable *)subscribeNext:( void (^)( id x))nextBlock {
RACSubscriber *o = [ RACSubscriber subscriberWithNext :nextBlock error : NULL completed : NULL ];
return [ self subscribe:o];
}
RACSubscriber.m:
+ ( instancetype )subscriberWithNext:( void (^)( id x))next error:( void (^)( NSError *error))error completed:( void (^)( void ))completed {
RACSubscriber *subscriber = [[ self alloc ] init ];
subscriber-> _next = [next copy ];
subscriber-> _error = [error copy ];
subscriber-> _completed = [completed copy ];
return subscriber;
}
RACDynamicSignal.m:
- (RACDisposable *)subscribe:(id)subscriber {
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;
}
第三步 查看发送信号的过程:
当执行订阅者的sendNext方法时,就会执行之前创建订阅者保存的那个nextBlock方法。
RACSubscriber.m:
- (void)sendNext:(id)value {
@synchronized (self) {
void (^nextBlock)(id) = [self.next copy];
if (nextBlock == nil) return;
nextBlock(value);
}
}
2.RACSubscriber
订阅者,它不是一个类而是一个协议,实现了这个协议的类都称为订阅者。
3.RACDisposable
执行订阅取消或者进行对资源的清理工作,dispose。
4.RACSubject
是一个继承RACSignal并且遵守RACSubscriber协议的类。所以这一个类不仅可以处理信号,还可以发送信号。因为RACSubject的subscribeNext方法内部有数组subscribers,可以保存所有的订阅者,而RACSubject的sendNext再发送信号的时候会遍历所有的订阅者,订阅者执行nextBlock。下面是RACSubject信号实现的具体细节:
- (RACDisposable *)subscribe:(id)subscriber {
NSCParameterAssert(subscriber != nil);
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
NSMutableArray *subscribers = self.subscribers;
@synchronized (subscribers) {
[subscribers addObject:subscriber];
}
return [RACDisposable disposableWithBlock:^{
@synchronized (subscribers) {
// Since newer subscribers are generally shorter-lived, search
// starting from the end of the list.
NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id obj, NSUInteger index, BOOL *stop) {
return obj == subscriber;
}];
if (index != NSNotFound) [subscribers removeObjectAtIndex:index];
}
}];
}
- (void)sendNext:(id)value {
[self enumerateSubscribersUsingBlock:^(id subscriber) {
[subscriber sendNext:value];
}];
}
5.RACReplaySubject
继承RACSubject,和RACSubject不同之处在于RACReplaySubject可以先发送信号,然后再订阅信号。原因在于RACReplaySubject的subscribe方法中遍历所有的订阅者,拿到当前订阅者发送数据。RACReplaySubject的sendNext方法是先保存值,然后再发送数据,RACSubject则是直接遍历发送数据。
RACReplaySubject.m
- (RACDisposable *)subscribe:(id)subscriber {
RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
@synchronized (self) {
for (id value in self.valuesReceived) {
if (compoundDisposable.disposed) return;
[subscriber sendNext:([value isKindOfClass:RACTupleNil.class] ? nil : value)];//订阅者发送数据
}
if (compoundDisposable.disposed) return;
if (self.hasCompleted) {
[subscriber sendCompleted];
} else if (self.hasError) {
[subscriber sendError:self.error];
} else {
RACDisposable *subscriptionDisposable = [super subscribe:subscriber];
[compoundDisposable addDisposable:subscriptionDisposable]
}
}
}];
[compoundDisposable addDisposable:schedulingDisposable];
return compoundDisposable;
}
- (void)sendNext:(id)value {
@synchronized (self) {
[self.valuesReceived addObject:value ?: RACTupleNil.tupleNil];//先保存值
[super sendNext:value]; //再发送数据
if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) {
[self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)];
}
}
}
6.RACMulticastConnection
连接类是为了当我们多次订阅同一个信号的时候,避免订阅信号的block中的代码被调用多次。
具体用法见例子7
7.RACCommand
这个类是负责处理事件的类,可以控制事件的传递以及数据的传递,监控事件的执行过程。
三.常用语法
1. 监听 KVO
1.1> 监听对象的属性变化
[RACObserve(self.scrollView, contentSize) subscribeNext:^(id x) {
}];
1.2> 监听Bool值改变
[RACObserve(self, bCheck) subscribeNext:^(id x) {
}];
1.3> 监听方法
监听某个方法被调用会触发
[[self rac_signalForSelector:@selector(viewDidAppear:)] subscribeNext:^(id x) {
}];
可以指定某个代理中的方法
[[self rac_signalForSelector:@selector(alertView:clickedButtonAtIndex:) fromProtocol:@protocol(UIAlertViewDelegate)] subscribeNext:^(RACTuple *tuple) {
}];
监听UITextField变化
[textField.rac_textSignal subscribeNext:^(NSString *text) {
//文本输入变化
}];
[[textField rac_signalForControlEvents:UIControlEventEditingChanged] subscribeNext:^(id x) {
//文本输入变化
}];
[[textField rac_signalForControlEvents:UIControlEventEditingDidEnd] subscribeNext:^(id x) {
//结束编辑
}];
RACObserve监听的对象属性返回值作为RAC监听对象属性的值
RAC(customBtn, hidden) = RACObserve(textField, hidden);
等价于:
[RACObserve(textField, hidden) subscribeNext:^(BOOL x) {
customBtn.hidden = x;
}]
2.事件
2.1> 按钮点击事件
[[submitBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
}];
2.2> 手势事件
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] init];
[[cancelTap rac_gestureSignal] subscribeNext:^(id x) {
}];
3.通知
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"postData" object:nil] subscribeNext:^(NSNotification *notification) {
}];
4.替代代理 回调类似于block用法
信号创建
RACSubject * moreSignal = [RACSubject subject];
信号发送
[moreSignal sendNext:<#(id)#>];
信号响应
[moreSignal subscribeNext:^(id x) {
}];
5.映射
map函数就是创建一个订阅者的映射并且返回数据,RAC监听对象属性的值,也就是customLabel.text根据textField的值来赋值,value的类型根据target监听属性值来定义
eg:textField的text是NSString类型.
map函数需要返回值,类型必须和等号左边的RAC的接受值一致,如果返回BOOL则crash
RAC(customLabel, text) = [textField.rac_textSignal map:^id(NSString *value) {
return value;
}];
[[textFild.rac_textSignal map:^id(id value) {
return @1;
}] subscribeNext:^(id x) {
NSLog(@"%@", x); //输出1,这个x是上面block中return返回值1
}];
6.过滤
6.1> filter
可以帮助你筛选出你需要的值
[[self.textFild.rac_textSignal filter:^BOOL(NSString *value) {
return [value length] > 3;
}] subscribeNext:^(id x) {
NSLog(@"x = %@", x);
}];
6.2> ignore
可以忽略某些值
[[self.textFild.rac_textSignal filter:^BOOL(NSString *value) {
return [value length] > 3;
}] subscribeNext:^(id x) {
NSLog(@"x = %@", x);
}];
6.3> take
从开始一共取几次信号. 从头
RACSubject * subject = [RACSubject subject];
[[subject take:2] subscribeNext:^(id x) {
NSLog(@"%@",x); // 1 2
}];
[subject sendNext:@"1"];
[subject sendNext:@"2"];
[subject sendNext:@"3"];
6.4> takeLast
取后面的值 必须是发送完成
RACSubject * subject = [RACSubject subject];
[[subject takeLast:2] subscribeNext:^(id x) {
NSLog(@"%@",x); // 2 3
}];
[subject sendNext:@"1"];
[subject sendNext:@"2"];
[subject sendNext:@"3"];
[subject sendCompleted];
6.5> takeUntil
当传入的某个信号发送完成,这样就不会再接收源信号的内容,或者发送任意数据也不会再接收
RACSubject * subject = [RACSubject subject];
RACSubject * signal = [RACSubject subject];
[[subject takeUntil:signal] subscribeNext:^(id x) {
NSLog(@"%@",x); //1 2
}];
[subject sendNext:@"1"];
[subject sendNext:@"2"];
[signal sendCompleted];
[subject sendNext:@"3"];
6.6> distinctUntilChanged
如果当前的值跟上一个值相同,这样就不会被订阅发送信号
RACSubject * subject = [RACSubject subject];
[[subject distinctUntilChanged] subscribeNext:^(id x) {
NSLog(@"%@",x); //A
}];
[subject sendNext:@"A"];
[subject sendNext:@"A"];
6.7> replay
replay方法会返回一个新的信号,当信号被多次订阅,如果通过[RACSignal createSignal:nil]创建那么调用replay不会重复执行源信号中的订阅代码,如果是通过[[RACSubject subject] replay]创建,订阅者将收到信号中所有的值。
RACSignal *signal= [[RACSignal createSignal:^RACDisposable *(id subscriber) {
NSLog(@"订阅代码被调用");
[subscriber sendNext:nil];
return nil;
}] replay];
[signal subscribeNext:^(id x) {
NSLog(@"开始订阅");
}];
[signal subscribeNext:^(id x) {
NSLog(@"开始订阅");
}];
打印如下:
2018-05-08 19:05:04.446126+0800 XLTestDemo[20002:1561273] 订阅代码被调用
2018-05-08 19:05:04.446399+0800 XLTestDemo[20002:1561273] 开始订阅
2018-05-08 19:05:04.446551+0800 XLTestDemo[20002:1561273] 开始订阅
类似功能的还有replayLast和replayLazily方法。
7.RACMulticastConnection
当我们多次订阅同一个信号的时候,避免订阅信号block中的代码被调用多次。
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
return nil;
}];
RACMulticastConnection *connection = [signal publish];//转化为连接类
[connection.signal subscribeNext:^(id x) {
}];
[connection.signal subscribeNext:^(id x) {
}];
[connection connect]; //链接
8.rac_liftSelector:withSignalsFromArray:
当进入一个页面需要发多次请求,当全部请求结束再执行更新UI,可以使用下面RAC方法,可以替代多线程GCD的dispatch_group_enter和dispatch_group_leave
参数1:请求结束执行的方法,参数个数必须是和参数二的数组信号个数一致,是信号发送的值
参数2: 数组 存放所有信号
rac_liftSelector:withSignalsFromArray:
9.组合
9.1> concat 数组组合
RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *concatenated = [letters concat:numbers];
[concatenated.signal subscribeNext:^(id x) {
NSLog(@"%@",x); // Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9
}];
9.2> merge 当多个信号执行同一种操作 使用merge
RACSubject *subject1 = [RACSubject subject];
RACSubject *subject2 = [RACSubject subject];
RACSignal *mergeSignal = [subject1 merge:subject2];
[mergeSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[subject1 sendNext:@"第一个位置调用"];
[subject1 sendNext:@"第二个位置调用"];
打印:
2018-05-08 15:57:03.949489+0800 BGMCommonDemo[13029:1321912] 第一个位置调用
2018-05-08 15:57:11.377246+0800 BGMCommonDemo[13029:1321912] 第二个位置调用
使用场景:例如一个对象的属性BOOL值isSend,有两个信号的返回值都是设置isSend
RACSubject *subject1 = [RACSubject subject];
RACSubject *subject2 = [RACSubject subject];
RAC(self, isSend) = [RACSignal merge:@[[subject1 sendNext:YES], [subject2 sendNext:NO]]];```
self.isSend会被赋值两次
9.3> zipWith 当希望两个信号都发出信号时才调用,并且会把两个信号的内容组成一个元组,和第8的作用非常一样
RACSubject *subject1 = [RACSubject subject];
RACSubject *subject2 = [RACSubject subject];
RACSignal *mergeSignal = [subject1 zipWith:subject2];
[mergeSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[subject1 sendNext:@"第一个位置调用"];
[subject1 sendNext:@"第二个位置调用"];
9.4> combineLatest 将多个信号合并起来,当希望两个信号都发出信号时才调用,和9.3作用一样
RACSubject *subject1 = [RACSubject subject];
RACSubject *subject2 = [RACSubject subject];
RACSignal *mergeSignal = [RACSignal combineLatest:@[subject1,subject2] reduce:^id(NSString * title1,NSString * title2){
NSLog(@"%@ -- %@",title1,title2); //第一个位置调用 -- 第二个位置调用
return @"返回值";
}];
[mergeSignal subscribeNext:^(id x) {
NSLog(@"%@",x); //返回值
}];
[subject1 sendNext:@"第一个位置调用"];
[subject2 sendNext:@"第二个位置调用"];
9.5> reduce
reduce是聚合的作用,讲多个信号分别发送的信号聚在一起返回。
###参考内容:
[ReactiveCocoa的GitHub官网](https://github.com/ReactiveCocoa/ReactiveCocoa)
[细说ReactiveCocoa的冷信号与热信号(一)](https://tech.meituan.com/talk-about-reactivecocoas-cold-signal-and-hot-signal-part-1.html)
[细说ReactiveCocoa的冷信号与热信号(二): 为什么要区分冷热信](https://tech.meituan.com/talk-about-reactivecocoas-cold-signal-and-hot-signal-part-2.html)
[细说ReactiveCocoa的冷信号与热信号(三): 怎么处理冷信号与热信号](https://tech.meituan.com/talk-about-reactivecocoas-cold-signal-and-hot-signal-part-3.html)