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;
  1. 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不会持有订阅者


xteerorcompleted.png

Rimplenentation Rxcsubject.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];
        }
    }];
}

十、创建信号简单调用

image.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];保存的。如果有 就把发送的消息,传递出去。
上面我们一个一个的都点进去看了他们的具体实现,现在来张图更加直观的看看这个过程


image.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内存泄漏问题

创建信号引起的循环引用情况

如下景几种简单的创建信号时引起的街环引用情况:.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") \
    })
image.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进行了持有,用一幅图来描述下刚才所说的关系:

image.png

如果这个signal是一个RACSignal,那么是没有任何问题的;如果是signal是一个RACSubject,那问题就来了。还记得前面说过的RACSignal和RACSubject的区别吗?RACSubject会持有订阅者,而RACSignal不会持有订阅者,如果signal是一个RACSubject,那么图应该是这样的:
image.png

很明显,产生了循环引用!!!到这里,也就解答了前面提出的三个问题的前两个: 对一个信号进行了map操作,那么最终会调用到bind。 如果源信号是RACSubject,由于RACSubject会持有订阅者,所以产生了循环引用(内存泄漏); 如果源信号是RACSignal,由于RACSignal不会持有订阅者,那么也就不存在循环引用。
image.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将最里面的参数传外界

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容