美文网首页iOS相关
iOS最新面试题解答最全-2023-ReactiveCocoa

iOS最新面试题解答最全-2023-ReactiveCocoa

作者: MoShengLive | 来源:发表于2023-02-12 10:24 被阅读0次

    一、简单介绍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将最里面的参数传外界

    相关文章

      网友评论

        本文标题:iOS最新面试题解答最全-2023-ReactiveCocoa

        本文链接:https://www.haomeiwen.com/subject/yualkdtx.html