iOS最新面试题解答最全-2023-ReactiveCocoa
一、简单介绍ReactiveCocoa
ReactiveCocoa(简称为RAC),是由Github开源的一个应用于iOS和OS开发的新框架,Cocoa是苹果整套框架的简称,因此很多苹果框架喜欢以Cocoa结尾
二、ReactiveCocoa作用
在我们iOS开发过程中,当某些事件响应的时候,需要处理某些业务逻辑,这些事件都用不同的方式来处理。
比如按钮的点击使用action,ScrollView滚动使用delegate,属性值改变使用KVO等系统提供的方式。
其实这些事件,都可以通过RAC处理
ReactiveCocoa为事件提供了很多处理方法,而且利用RAC处理事件很方便,可以把要处理的事情,和监听的事情的代码放在一起,这样非常方便我们管理,就不需要跳到对应的方法里。非常符合我们开发中高聚合,低耦合的思想。
三、编程思想
先简单介绍下目前咱们已知的编程思想。
1 面向过程:处理事情以过程为核心,一步一步的实现。
2 面向对象:万物皆对象
3 链式编程思想:是将多个操作(多行代码)通过点号(.)链接在一起成为一句代码,使代码可读性好。a(1).b(2).c(3)
链式编程特点:方法的返回值是block,block必须有返回值(本身对象),block参数(需要操作的值)
代表:masonry框架。
4 响应式编程思想:不需要考虑调用顺序,只需要知道考虑结果,类似于蝴蝶效应,产生一个事件,会影响很多东西,这些事件像流一样的传播出去,然后影响结果,借用面向对象的一句话,万物皆是流。
代表:KVO运用。
5 函数式编程思想:是把操作尽量写成一系列嵌套的函数或者方法调用。
函数式编程本质:就是往方法中传入Block,方法中嵌套Block调用,把代码聚合起来管理
函数式编程特点:每个方法必须有返回值(本身对象),把函数或者Block当做参数,block参数(需要操作的值)block返回值(操作结果)
代表:ReactiveCocoa。
四、ReactiveCocoa编程思想
ReactiveCocoa结合了几种编程风格:
函数式编程(Functional Programming)
响应式编程(Reactive Programming)
所以,你可能听说过ReactiveCocoa被描述为函数响应式编程(FRP)框架。
以后使用RAC解决问题,就不需要考虑调用顺序,直接考虑结果,把每一次操作都写成一系列嵌套的方法中,使代码高聚合,方便管理。
五、ReactiveCocoa常见类
RACSiganl:信号类,一般表示将来有数据传递,只要有数据改变,信号内部接收到数据,就会马上发出数据。
信号类(RACSiganl),只是表示当数据改变时,信号内部会发出数据,它本身不具备发送信号的能力,而是交给内部一个订阅者去发出。
默认一个信号都是冷信号,也就是值改变了,也不会触发,只有订阅了这个信号,这个信号才会变为热信号,值改变了才会触发。
如何订阅信号:调用信号RACSignal的subscribeNext就能订阅。
RACCommand:RAC中用于处理事件的类,可以把事件如何处理,事件中的数据如何传递,包装到这个类中,他可以很方便的监控事件的执行过程。
使用场景:监听按钮点击,网络请求
RACCommand简单使用
```
// 一、RACCommand使用步骤:
// 1.创建命令 initWithSignalBlock:(RACSignal * (^)(id input))signalBlock
// 2.在signalBlock中,创建RACSignal,并且作为signalBlock的返回值
// 3.执行命令 - (RACSignal *)execute:(id)input
// 二、RACCommand使用注意:
// 1.signalBlock必须要返回一个信号,不能传nil.
// 2.如果不想要传递信号,直接创建空的信号[RACSignal empty];
// 3.RACCommand中信号如果数据传递完,必须调用[subscriber sendCompleted],这时命令才会执行完毕,否则永远处于执行中。
// 三、RACCommand设计思想:内部signalBlock为什么要返回一个信号,这个信号有什么用。
// 1.在RAC开发中,通常会把网络请求封装到RACCommand,直接执行某个RACCommand就能发送请求。
// 2.当RACCommand内部请求到数据的时候,需要把请求的数据传递给外界,这时候就需要通过signalBlock返回的信号传递了。
// 四、如何拿到RACCommand中返回信号发出的数据。
// 1.RACCommand有个执行信号源executionSignals,这个是signal of signals(信号的信号),意思是信号发出的数据是信号,不是普通的类型。
// 2.订阅executionSignals就能拿到RACCommand中返回的信号,然后订阅signalBlock返回的信号,就能获取发出的值。
// 五、监听当前命令是否正在执行executing
// 六、使用场景,监听按钮点击,网络请求
// 1.创建命令
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
NSLog(@"执行命令");
// 创建空信号,必须返回信号
// return [RACSignal empty];
// 2.创建信号,用来传递数据
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"请求数据"];
// 注意:数据传递完,最好调用sendCompleted,这时命令才执行完毕。
[subscriber sendCompleted];
return nil;
}];
}];
// 强引用命令,不要被销毁,否则接收不到数据
_conmmand = command;
// 3.执行命令
[self.conmmand execute:@1];
// 4.订阅RACCommand中的信号
[command.executionSignals subscribeNext:^(id x) {
[x subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
}];
// RAC高级用法
// switchToLatest:用于signal of signals,获取signal of signals发出的最新信号,也就是可以直接拿到RACCommand中的信号
[command.executionSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 5.监听命令是否执行完毕,默认会来一次,可以直接跳过,skip表示跳过第一次信号。
[[command.executing skip:1] subscribeNext:^(id x) {
if ([x boolValue] == YES) {
// 正在执行
NSLog(@"正在执行");
}else{
// 执行完成
NSLog(@"执行完成");
}
}];
RACScheduler:RAC中的队列,用GCD封装的。
RACUnit :表⽰stream不包含有意义的值,也就是看到这个,可以直接理解为nil.
RACEvent: 把数据包装成信号事件(signal event)。它主要通过RACSignal的-materialize来使用,然并卵。
RACSubscriber:表示订阅者的意思,用于发送信号,这是一个协议,不是一个类,只要遵守这个协议,并且实现方法才能成为订阅者。通过create创建的信号,都有一个订阅者,帮助他发送数据。
RACDisposable:用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它。
使用场景:不想监听某个信号时,可以通过它主动取消订阅信号。
RACSubject:RACSubject:信号提供者,自己可以充当信号,又能发送信号。
使用场景:通常用来代替代理,有了它,就不必要定义代理了。
RACReplaySubject:重复提供信号类,RACSubject的子类。
* RACReplaySubject与RACSubject区别:
* RACReplaySubject可以先发送信号,在订阅信号,RACSubject就不可以。
* 使用场景一:如果一个信号每被订阅一次,就需要把之前的值重复发送一遍,使用重复提供信号类。
* 使用场景二:可以设置capacity数量来限制缓存的value的数量,即只缓充最新的几个值。
RACTuple:元组类,类似NSArray,用来包装值.
RACSequence:RAC中的集合类,用于代替NSArray,NSDictionary,可以使用它来快速遍历数组和字典。
RACMulticastConnection:用于当一个信号,被多次订阅时,为了保证创建信号时,避免多次调用创建信号中的block,造成副作用,可以使用这个类处理。
使用注意:RACMulticastConnection通过RACSignal的-publish或者-muticast:方法创建.
RACMulticastConnection简单使用:
六、ReactiveCocoa开发中常见用法
1 代替代理:
rac_signalForSelector:用于替代代理。
[[self rac_signalForSelector:@selector(tableView:didSelectRowAtIndexPath:) fromProtocol:@protocol(UITableViewDelegate)] subscribeNext:^(id x) {
}];
2 代替KVO :
rac_valuesAndChangesForKeyPath:用于监听某个对象的属性改变。
//方法1
[[self rac_valuesAndChangesForKeyPath:@"title" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
}];
//方法2
[[self rac_valuesForKeyPath:@"title" observer:nil] subscribeNext:^(id x) {
}];
//方法3
[RACObserve(self, title) subscribeNext:^(id x) {
}];
3 监听事件:
rac_signalForControlEvents:用于监听某个事件。
按钮点击
[[self.button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
}];
手势事件
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]init];
[tap.rac_gestureSignal subscribeNext:^(id x) {
}];
4 代替通知:
rac_addObserverForName:用于监听某个通知。
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"name" object:nil] subscribeNext:^(id x) {
}];
5 监听文本框文字改变:
rac_textSignal:只要文本框发出改变就会发出这个信号。
[[self.textField rac_textSignal] subscribeNext:^(NSString * _Nullable x) {
}];
6 处理当界面有多次请求时,需要都获取到数据时,才能展示界面
rac_liftSelector:withSignalsFromArray:Signals:当传入的Signals(信号数组),每一个signal都至少sendNext过一次,就会去触发第一个selector参数的方法。
使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据。
7 定时器
//定时器
RACDisposable * disposable = [[RACSignal interval:1 onScheduler:[RACScheduler scheduler]] subscribeNext:^(id x) {
}];
//释放定时器
[disposable dispose];
8.集合遍历RACSequence
//默认在子线程中遍历
NSArray *numbers = @[@"1",@"2",@"3",@"4"];
[numbers.rac_sequence.signal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
//放在主线程中遍历
[[numbers.rac_sequence.signal deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(id _Nullable x) {
}];
9.映射Map(生成新的值)flattenMap(生成新的信号)
map(生成新的值)
NSArray * newNumbers = [numbers.rac_sequence map:^id(id value) {
return [NSString stringWithFormat:@"numbers: %@",value];
}].array;
flattenMap(生成新的信号)
// 创建信号
RACSubject *subject = [RACSubject subject];
// 绑定信号
RACSignal *bindSignal = [subject flattenMap:^RACStream *(id value) {
// value: 就是源信号发送的内容
// 返回信号用来包装成修改内容的值
return [RACReturnSignal return:value];
}];
// flattenMap中返回的是什么信号,订阅的就是什么信号(那么,x的值等于value的值,如果我们操纵value的值那么x也会随之而变)
// 订阅信号
[bindSignal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
// 发送数据
[subject sendNext:@"123"];
9.延时执行
throttle:延时调用block(subscribeNext)
[[[self.button rac_signalForControlEvents:UIControlEventTouchUpInside] throttle:5] subscribeNext:^(id x) {
}];
delay:延迟调用
[[[self.button rac_signalForControlEvents:UIControlEventTouchUpInside] delay:5] subscribeNext:^(id x) {
}];
timeout:超时后则不调用block
[[self.signal timeout:5 onScheduler:[RACScheduler scheduler]] subscribeNext:^(id x) {
}];
延时执行
[[RACScheduler scheduler] after:[NSDate dateWithTimeIntervalSinceNow:3] schedule:^{
}];
10.过滤
filter接受满足条件的信号
[[numbers.rac_sequence.signal filter:^BOOL(id value) {
return [value integerValue] > 3;
}] subscribeNext:^(id x) {
}];
skip跳过几个信号
// skip:后边传入要跳过几个信号
RACSubject *subject = [RACSubject subject];
[[subject skip:2] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@3];
distinctUntilChanged新值与旧值不一样则接收
11.RAC组合
combine场景:账号和密码都有值,登录按钮才可点击
//reduce里的参数一定要和combineLatest数组里的一一对应。
RACSignal *combinSignal = [RACSignal combineLatest:@[self.accountField.rac_textSignal, self.pwdField.rac_textSignal] reduce:^id(NSString *account, NSString *pwd){ NSLog(@"%@ %@", account, pwd);
return @(account.length && pwd.length);
}];
RAC(self.loginBtn, enabled) = combinSignal;
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:@"上部分"];
concat:串行执行,第一个信号必须要调用sendCompleted
// 创建信号A
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"上部分数据"];
[subscriber sendCompleted]; // 必须要调用sendCompleted方法!
return nil;
}];
// 创建信号B,
RACSignal *signalsB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"下部分数据"];
return nil;
}];
// 创建组合信号
RACSignal *concatSignal = [signalA concat:signalsB];
// 订阅组合信号
[concatSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
七、ReactiveCocoa常见宏
1.RAC(TARGET, [KEYPATH, [NIL_VALUE]]):用于给某个对象的某个属性绑定。
// 只要文本框文字改变,就会修改label的文字
RAC(self.labelView,text) = _textField.rac_textSignal;
- RACObserve(self, name):监听某个对象的某个属性,返回的是信号。
[RACObserve(self.view, center) subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
3 @weakify(Obj)和@strongify(Obj),一般两个都是配套使用,解决循环引用问题.
八、RAC 怎么区分热信号和冷信号的,怎么相互转换的?
Hot Observables(热信号)和Cold Observables(冷信号)的区别:
Hot Observables是主动的,尽管你并没有订阅事件,但是它会时刻推送,就像鼠标移动;而Cold Observables是被动的,只有当你订阅的时候,它才会发布消息。
Hot Observables可以有多个订阅者,是一对多,集合可以与订阅者共享信息;而Cold Observables只能一对一,当有不同的订阅者,消息是重新完整发送
热信号相当于直播,冷信号相当于点播
RACSubject及其子类是热信号
RACSignal排除RACSubject类以外的是冷信号
冷信号与热信号的本质区别在于是否保持状态,冷信号的多次订阅是不保持状态的,而热信号是保持状态的
RACSignal和RACSubject虽然都是信号,但是它们有一个本质的区别: RACSubject会持有订阅者(因为RACSubject是热信号,为了保证未来有事件发送的时候,订阅者可以收到信息,所以需要对订阅者保持状态,做法就是持有订阅者),而RACSignal不会持有订阅者
![](https://img.haomeiwen.com/i2789535/4346d0a8f8c1e4d1.png)
![](https://img.haomeiwen.com/i2789535/d72af695317ba936.png)
@implementation RACSignal (Subscription)
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCAssert(NO, @"This method must be overridden by subclasses");
return nil;
}
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
return [self subscribe:o];
}
@implementation RACSubject
#pragma mark Lifecycle
+ (instancetype)subject {
return [[self alloc] init];
}
- (id)init {
self = [super init];
if (self == nil) return nil;
_disposable = [RACCompoundDisposable compoundDisposable];
_subscribers = [[NSMutableArray alloc] initWithCapacity:1];
return self;
}
- (void)dealloc {
[self.disposable dispose];
}
#pragma mark Subscription
- (RACDisposable *)subscribe:(id<RACSubscriber>)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<RACSubscriber> obj, NSUInteger index, BOOL *stop) {
return obj == subscriber;
}];
if (index != NSNotFound) [subscribers removeObjectAtIndex:index];
}
}];
}
十、创建信号简单调用
![](https://img.haomeiwen.com/i2789535/835af3c2d1436493.png)
RACSignal
先从最简单的RACSignal开始,我们先来看看它是怎么创建的
-(void)creatSigal{
RACSignal *sigal=[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"你个小样"];
return nil;
}];
[sigal subscribeNext:^(id _Nullable x) {
NSLog(@"传递的数据是:----%@",x);
}];
}
上面就创建了一个信号,并且订阅信号,还发送消息了。创建是成功了,但是它具体的原来还不知道啊。不要慌,我们点进方法看看。
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
RACDynamicSignal *signal = [[self alloc] init];
signal->_didSubscribe = [didSubscribe copy];
return [signal setNameWithFormat:@"+createSignal:"];
}
它其实就是一个信号,先初始化了RACDynamicSignal,然后下面_didSubscribe这个熟悉嘛,是不是有点像订阅信号,RACDynamicSignal持有了didSubscribe,最后返回了一个信号。这个方法看完了,有一个疑问,didSubscribe这个是用来干嘛的?接着往下看订阅信号的方法
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
return [self subscribe:o];
}
第一行很容易理解,就是创建RACSubscriber(订阅者),点进去看看它里面的实现
@interface RACSubscriber ()
// These callbacks should only be accessed while synchronized on self.
@property (nonatomic, copy) void (^next)(id value);
@property (nonatomic, copy) void (^error)(NSError *error);
@property (nonatomic, copy) void (^completed)(void);
@property (nonatomic, strong, readonly) RACCompoundDisposable *disposable;
@end
@implementation RACSubscriber
#pragma mark Lifecycle
+ (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;
}
我们发现,它初始化了订阅者,然后订阅者持有了next这个block,持有了error、completed,很简单。点进去看[self subscribe:o];的实现
-
(RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCParameterAssert(subscriber != nil);RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
//看的有点懵,上面的对象都不知道是什么 但是didSubscribe这个熟悉啊,在创建信号的时候RACDynamicSignal持有didSubscribe
if (self.didSubscribe != NULL) {
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
//这里调用didSubscribe方法,并且把刚才传入的subscriber调用出去
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
[disposable addDisposable:innerDisposable];
}];
[disposable addDisposable:schedulingDisposable];
}
return disposable;
}
现在我们最开始的疑问解开了,didSubscribe的调用。这个方法里能看懂的就是,先判断有没有didSubscribe,有的话就执行这个方法,参数是subscriber订阅者。后面我们在说RACDisposable。
- (void)sendNext:(id)value {
@synchronized (self) {
void (^nextBlock)(id) = [self.next copy];
if (nextBlock == nil) return;
nextBlock(value);
}
}
上面是订阅者发送消息的方法实现,判断有没有nextblock,而这里的nextblock也正是subscriber->_next = [next copy];保存的。如果有 就把发送的消息,传递出去。
上面我们一个一个的都点进去看了他们的具体实现,现在来张图更加直观的看看这个过程
![](https://img.haomeiwen.com/i2789535/85f33af46c9393f1.png)
这里面有要说明的是:didSubscribe的参数是RACSubscriber,而在我们发送消息的时候, [subscriber sendNext:@"你个小样"]; 这里的订阅者就是didSubscribe传入的,而订阅者持有nextblock,所以nextBlock(value);消息就传入到订阅的block中了。这就是创建RACSignal的一个流程。
RACSubject
-(void)creatracSubject{
RACSubject *subject=[RACSubject subject];
[subject subscribeNext:^(id _Nullable x) {
NSLog(@"收到的消息是:---%@",x);
}];
//发送消息
[subject sendNext:@"666 666"];
}
LBDaySurgery(Dev)[10505:3899054] 收到的消息是:---666 666
我们点进方法看看它的实现原理
+ (instancetype)subject {
return [[self alloc] init];
}
- (instancetype)init {
self = [super init];
if (self == nil) return nil;
_disposable = [RACCompoundDisposable compoundDisposable];
_subscribers = [[NSMutableArray alloc] initWithCapacity:1];
return self;
}
创建subject对象时,初始化了一个取消信号和一个数组,从名字我们能看出这是用来存放订阅者的。
接着看订阅信号的实现
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
return [self subscribe:o];
}
- (RACDisposable *)subscribe:(id<RACSubscriber>)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];
}
[disposable addDisposable:[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<RACSubscriber> obj, NSUInteger index, BOOL *stop) {
return obj == subscriber;
}];
if (index != NSNotFound) [subscribers removeObjectAtIndex:index];
}
}]];
return disposable;
}
从上面这段代码能看到,是把订阅者都加入到subscribers数组中了。也表示subject强引用subscriber
我们再看发布消息的实现
- (void)enumerateSubscribersUsingBlock:(void (^)(id<RACSubscriber> subscriber))block {
NSArray *subscribers;
@synchronized (self.subscribers) {
subscribers = [self.subscribers copy];
}
//遍历subscribers中的订阅者,然后block传递subscriber
for (id<RACSubscriber> subscriber in subscribers) {
block(subscriber);
}
}
#pragma mark RACSubscriber
- (void)sendNext:(id)value {
[self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
//这里和之前RACSignal发送消息相同
[subscriber sendNext:value];
}];
}
1.创建的subject内部会创建数组_subscribers,用来保存所有的订阅者。
2.订阅信息的时候会创建订阅者
3.发送消息的时候,会依次发送。
九、RAC坑
RAC循环调用问题/RAC内存泄漏问题
创建信号引起的循环引用情况
![](https://img.haomeiwen.com/i2789535/6f574b2a09667c16.png)
RACObserve引发
- (void)viewDidLoad
{
[super viewDidLoad];
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { //1
MTModel *model = [[MTModel alloc] init]; // MTModel有一个名为的title的属性
[subscriber sendNext:model];
[subscriber sendCompleted];
return nil;
}];
self.flattenMapSignal = [signal flattenMap:^RACStream *(MTModel *model) { //2
return RACObserve(model, title);
}];
[self.flattenMapSignal subscribeNext:^(id x) { //3
NSLog(@"subscribeNext - %@", 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") \
})
![](https://img.haomeiwen.com/i2789535/9934b881daabb51a.png)
意:2是间接持有,从逻辑上来讲,flattenMapSignal会有一个didSubscribeBlock,为了让传递给flattenMap操作的block有意义,didSubscribeBlock会对该block进行持有,从而也就间接持有了self,感兴趣的读者可以去看下相关源码。
OK,找到了问题所在,解决起来也就简单了,使用@weakify和@strongify
RACSubject引起的循环
- (void)viewDidLoad {
[super viewDidLoad];
RACSubject *subject = [RACSubject subject]; //1
[subject.rac_willDeallocSignal subscribeCompleted:^{ //2
NSLog(@"subject dealloc");
}];
[subject subscribeNext:^(id x) { //3
NSLog(@"next = %@", x);
}];
[subject sendNext:@1]; //4
}
1.创建一个RACSubject的实例;
2.订阅subject的dealloc信号,在subject被释放的时候会发送完成信号;
3.订阅subject;
4.使用subject发送一个值。
2016-06-13 09:15:25.426 RAC[5366:245360] next = 1
2016-06-13 09:15:25.428 RAC[5366:245360] subject dealloc
工作相当良好,接下来改造下程序,要求对subject发送的所有值进行乘3,这用map很容易就实现了。
- (void)viewDidLoad {
[super viewDidLoad];
RACSubject *subject = [RACSubject subject];
[subject.rac_willDeallocSignal subscribeCompleted:^{
NSLog(@"subject dealloc");
}];
[[subject map:^id(NSNumber *value) {
return @([value integerValue] * 3);
}] subscribeNext:^(id x) {
NSLog(@"next = %@", x);
}];
[subject sendNext:@1];
}
跟之前大体不变,只是对subject进行了map操作然后再订阅,看下输出结果:
2016-06-13 09:21:42.450 RAC[5404:248584] next = 3
同样代码将RACSubject换成RACSignal是可以释放掉的
虽然得出了结论,但是留下的疑问也是不少,如果你希望知道这其中的缘由,请继续往下看。 简单来说,留下的疑问有:
1.为什么对RACSubject的实例进行map操作之后会产生内存泄漏?
2.为什么RACSignal不管是否有map操作,都不会产生内存泄漏?
3.针对第一个问题,为什么发送完成可以修复内存泄漏?
源码分析
1.创建信号量 RACSubject *subject = [RACSubject subject];
@implementation RACSubject
#pragma mark Lifecycle
+ (instancetype)subject {
return [[self alloc] init];
}
- (id)init {
self = [super init];
if (self == nil) return nil;
_disposable = [RACCompoundDisposable compoundDisposable];
_subscribers = [[NSMutableArray alloc] initWithCapacity:1];
return self;
}
2.[[subject map:^id(NSNumber *value) {
//将@([value integerValue] * 3)传给RACReturnSignal->_value
return @([value integerValue] * 3);
}] subscribeNext:^(id x) {
NSLog(@"next = %@", x);
}];
@implementation RACStream (Operations)
- (instancetype)map:(id (^)(id value))block {
NSCParameterAssert(block != nil);
Class class = self.class;
return [[self flattenMap:^(id value) {
//block(value)执行返回结果 @([value integerValue] * 3);
return [class return:block(value)];
}] setNameWithFormat:@"[%@] -map:", self.name];
}
- (instancetype)flattenMap:(RACStream * (^)(id value))block {
Class class = self.class;
/**
RACSignal *newSignal = [orgSignal bind:^RACStreamBindBlock{
RACStreamBindBlock bindBlock = ^(id value, BOOL *stop) {
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@(2*[value intValue])];
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
JCLogD(@"Bind disposed");
}];
}];
return signal;
};
return bindBlock;
}];
*/
//传递一个(RACStreamBindBlock (^)(void))类型block
return [[self bind:^{
//返回RACStreamBindBlock类型block
return ^(id value, BOOL *stop) {
/**
调用flattenMap时候传进来的block是这个:
^(id value) {
return [class return:block(value)];
}
id stream = block(value)执行 return [class return:block(value)];
RACReturnSignal *signal = [[self alloc] init];
signal->_value = value;
stream就是RACReturnSignal类型signal
*/
//block执行了返回一个RACStream类型信号
id stream = block(value) ?: [class empty];
NSCAssert([stream isKindOfClass:RACStream.class], @"Value returned from -flattenMap: is not a stream: %@", stream);
return stream;
};
}] setNameWithFormat:@"[%@] -flattenMap:", self.name];
}
@implementation RACSignal (RACStream)
- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block {
NSCParameterAssert(block != NULL);
/*
* -bind: should:
*
* 1. Subscribe to the original signal of values.
* 2. Any time the original signal sends a value, transform it using the binding block.
* 3. If the binding block returns a signal, subscribe to it, and pass all of its values through to the subscriber as they're received.
* 4. If the binding block asks the bind to terminate, complete the _original_ signal.
* 5. When _all_ signals complete, send completed to the subscriber.
*
* If any signal sends an error at any point, send that to the subscriber.
*/
//创建一个signal给外界
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
//执行block,返回RACStreamBindBlock类型block
/**
- (instancetype)flattenMap:(RACStream * (^)(id value))block
RACStreamBindBlock bindingBlock = block();执行block返回RACStreamBindBlock类型
以下就是bindingBlock赋值
return ^(id value, BOOL *stop) {
//block执行了返回一个RACStream类型信号
id stream = block(value) ?: [class empty];
NSCAssert([stream isKindOfClass:RACStream.class], @"Value returned from -flattenMap: is not a stream: %@", stream);
return stream;
};
*/
RACStreamBindBlock bindingBlock = block();
NSMutableArray *signals = [NSMutableArray arrayWithObject:self];
RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
void (^completeSignal)(RACSignal *, RACDisposable *) = ^(RACSignal *signal, RACDisposable *finishedDisposable) {
BOOL removeDisposable = NO;
@synchronized (signals) {
[signals removeObject:signal];
if (signals.count == 0) {
[subscriber sendCompleted];
[compoundDisposable dispose];
} else {
removeDisposable = YES;
}
}
if (removeDisposable) [compoundDisposable removeDisposable:finishedDisposable];
};
void (^addSignal)(RACSignal *) = ^(RACSignal *signal) {
@synchronized (signals) {
[signals addObject:signal];
}
RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
[compoundDisposable addDisposable:selfDisposable];
//订阅新建的信号RACReturnSignal
RACDisposable *disposable = [signal subscribeNext:^(id x) {
//这里value直接[subscriber sendNext:self.value];也就将外界value传过来
[subscriber sendNext:x];
} error:^(NSError *error) {
[compoundDisposable dispose];
[subscriber sendError:error];
} completed:^{
@autoreleasepool {
completeSignal(signal, selfDisposable);
}
}];
selfDisposable.disposable = disposable;
};
@autoreleasepool {
RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
[compoundDisposable addDisposable:selfDisposable];
//源信号量订阅信息
RACDisposable *bindingDisposable = [self subscribeNext:^(id x) {
// Manually check disposal to handle synchronous errors.
if (compoundDisposable.disposed) return;
BOOL stop = NO;
//这里返回一个信号,执行了 block(value)
/**
- (instancetype)flattenMap:(RACStream * (^)(id value))block
signal = bindingBlock(x, &stop);表达式就是以下这个方法
id stream = block(value) ?: [class empty];
- (instancetype)map:(id (^)(id value))block
这里block就是=map方法里^(id value) {
//block(value)执行返回结果 @([value integerValue] * 3);
return [class return:block(value)];
而 stream=重新创建一个RACReturnSignal类型信号返回给外界
}
*/
// x就是代表[[subject map:^id(NSNumber *value) 的value值,比如[subject sendNext:@1];表示value就是1
id signal = bindingBlock(x, &stop);
@autoreleasepool {
//这里传入RACReturnSignal类型信号,新建的
if (signal != nil) addSignal(signal);
if (signal == nil || stop) {
[selfDisposable dispose];
completeSignal(self, selfDisposable);
}
}
} error:^(NSError *error) {
[compoundDisposable dispose];
[subscriber sendError:error];
} completed:^{
@autoreleasepool {
completeSignal(self, selfDisposable);
}
}];
selfDisposable.disposable = bindingDisposable;
}
return compoundDisposable;
}] setNameWithFormat:@"[%@] -bind:", self.name];
}
OK,了解了bind操作的用途,也是时候回归主题了——内存是怎么泄露的。 首先我们看到,在didSubscribe的开头,就创建了一个数组signals,并且持有了self,也就是源信号
NSMutableArray *signals = [NSMutableArray arrayWithObject:self];
接下来会对源信号进行订阅:
RACDisposable *bindingDisposable = [self subscribeNext:^(id x) {
// Manually check disposal to handle synchronous errors.
if (compoundDisposable.disposed) return;
BOOL stop = NO;
id signal = bindingBlock(x, &stop);
@autoreleasepool {
if (signal != nil) addSignal(signal);
if (signal == nil || stop) {
[selfDisposable dispose];
completeSignal(self, selfDisposable);
}
}
} error:^(NSError *error) {
[compoundDisposable dispose];
[subscriber sendError:error];
} completed:^{
@autoreleasepool {
completeSignal(self, selfDisposable);
}
}];
订阅者会持有nextBlock、errorBlock、completedBlock三个block,为了简单,我们只讨论nextBlock。 从nextBlock中的completeSignal(self, selfDisposable);这一行代码可以看出,nextBlock对self,也就是源信号进行了持有,再看到if (signal != nil) addSignal(signal);这一行,nextBlock对addSignal进行了持有,addSignal是在订阅self之前定义的一个block。
void (^addSignal)(RACSignal *) = ^(RACSignal *signal) {
@synchronized (signals) {
[signals addObject:signal];
}
RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
[compoundDisposable addDisposable:selfDisposable];
RACDisposable *disposable = [signal subscribeNext:^(id x) {
[subscriber sendNext:x];
} error:^(NSError *error) {
[compoundDisposable dispose];
[subscriber sendError:error];
} completed:^{
@autoreleasepool {
completeSignal(signal, selfDisposable);
}
}];
selfDisposable.disposable = disposable;
};
addSignal这个block里面对一开始创建的数组signals进行了持有,用一幅图来描述下刚才所说的关系:
![](https://img.haomeiwen.com/i2789535/75135720ed3b23d7.png)
如果这个signal是一个RACSignal,那么是没有任何问题的;如果是signal是一个RACSubject,那问题就来了。还记得前面说过的RACSignal和RACSubject的区别吗?RACSubject会持有订阅者,而RACSignal不会持有订阅者,如果signal是一个RACSubject,那么图应该是这样的:
![](https://img.haomeiwen.com/i2789535/21bd9e6547acb0bf.png)
很明显,产生了循环引用!!!到这里,也就解答了前面提出的三个问题的前两个: 对一个信号进行了map操作,那么最终会调用到bind。 如果源信号是RACSubject,由于RACSubject会持有订阅者,所以产生了循环引用(内存泄漏); 如果源信号是RACSignal,由于RACSignal不会持有订阅者,那么也就不存在循环引用。
![](https://img.haomeiwen.com/i2789535/be4212a6d7b28505.png)
链接
还剩下最后一个问题:如果源信号是RACSubject,为什么发送完成可以修复内存泄漏? 来看下订阅者收到完成信号之后干了些什么:
RACDisposable *bindingDisposable = [self subscribeNext:^(id x) {
//...
} error:^(NSError *error) {
//...
} completed:^{
@autoreleasepool {
completeSignal(self, selfDisposable);
}
}];
很简单,只是调用了一下completeSignal这个block。再看下这个block内部在干嘛:
void (^completeSignal)(RACSignal *, RACDisposable *) = ^(RACSignal *signal, RACDisposable *finishedDisposable) {
BOOL removeDisposable = NO;
@synchronized (signals) {
[signals removeObject:signal]; //1
if (signals.count == 0) {
[subscriber sendCompleted]; //2
[compoundDisposable dispose]; //3
} else {
removeDisposable = YES;
}
}
if (removeDisposable) [compoundDisposable removeDisposable:finishedDisposable]; //4
};
//1这里从signals这个数组中移除传入的signal,也就断掉了signals持有subject这条线。 //2、//3、//4其实干的事情差不多,都是拿到对应的disposable调用dispose,这样资源就得到了回收,subject就不会再持有subscriber,subscriber也会对自己的nextBlock、errorBlock、completedBlock三个block置为nil,就不会存在引用关系,所有的对象都得到了释放。 有兴趣的同学可以去了解下RACDisposable,它也是ReactiveCocoa中的重要一员,对理解源码有很大的帮助。 map只是一个很典型的操作,其实在ReactiveCocoa的实现中,几乎所有的操作底层都会调用到bind这样一个方法,包括但不限于: map、filter、merge、combineLatest、flattenMap ……
所以在使用ReactiveCocoa的时候也一定要仔细,对信号操作完成之后,记得发送完成信号,不然可能在不经意间就导致了内存泄漏。
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
[subscriber sendCompleted]; // 保证源信号发送完成
return nil;
}];
RACSignal *replaySignal = [signal replay]; // 这里返回的其实是一个RACReplaySubject
[[replaySignal map:^id(NSNumber *value) {
return @([value integerValue] * 3);
}] subscribeNext:^(id x) {
NSLog(@"subscribeNext - %@", x);
}];
总之,一句话:使用ReactiveCocoa必须要保证信号发送完成或者发送错误。
补充map方法
对于研究RAC框架来说,map方法比较晦涩难懂,自己也是看源码半天来的
接下来直接看方法,主要是看方法参数block
/// A block which accepts a value from a RACStream and returns a new instance
/// of the same stream class.
///
/// Setting `stop` to `YES` will cause the bind to terminate after the returned
/// value. Returning `nil` will result in immediate termination.
typedef RACStream * (^RACStreamBindBlock)(id value, BOOL *stop);
- (instancetype)map:(id (^)(id value))block
- (instancetype)flattenMap:(RACStream * (^)(id value))block
- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block
分析
1.然后在bind方法直接第一次调用block,返回的RACStreamBindBlock
RACStreamBindBlock bindingBlock = block();
执行过后就是flattenMap方法里:^(id value, BOOL *stop) {
/**
调用flattenMap时候传进来的block是这个:
^(id value) {
return [class return:block(value)];
}
id stream = block(value)执行 return [class return:block(value)];
RACReturnSignal *signal = [[self alloc] init];
signal->_value = value;
stream就是RACReturnSignal类型signal
*/
//block执行了返回一个RACStream类型信号
id stream = block(value) ?: [class empty];
NSCAssert([stream isKindOfClass:RACStream.class], @"Value returned from -flattenMap: is not a stream: %@", stream);
return stream;
};
2.在bind方法里源信号的didSubscribeblock里执行第二次block()
id signal = bindingBlock(x, &stop);
从上面block里看bindingBlock就是flattenMap方法里block的block,也就是:外界调用flattenMap传过来的block参数
也就是bindingBlock =^(id value) {
//block(value)执行返回结果 @([value integerValue] * 3);
return [class return:block(value)];
}
3,id signal = [class return:block(value)] 创建RACReturnSignal类型的signal
4,value就是从bind里面参数传回来的
map:方法实现方案是将block一个类型传给下一个方法,上一个方法参数类型同时也是下一个方法调用参数block类型返回值类型,最后在bind方法直接几次(有几次调用,就几次调用block,比如这次map方法最终调用到bind是两次,而再bind方法直接几次bloc)block,最终执行最初的map传过去block,通过block将最里面的参数传外界