美文网首页iOS 进阶开发
探究ReactiveCocoa底层之RACSubject设计流程

探究ReactiveCocoa底层之RACSubject设计流程

作者: 溪浣双鲤 | 来源:发表于2019-06-16 13:34 被阅读169次
    鸡汤前言:
    我们先养成习惯,然后习惯再养成我们!
    有些事不是看到了希望才去坚持,而是因为坚持而看到了希望!
    

    直接上今天的干货部分,来深入了解RACSubject的底层实现及设计思想。

    一、探究RACSubject底层设计思想


    首先创建一个新工程,然后pod ReactiveObjC,实现一个简单的RACSubject使用**

    //
    //  ViewController.m
    //  RACSubject
    //  RACSubject深入探究
    //  Created by battleMage on 2019/6/16.
    //  Copyright © 2019 battleMage. All rights reserved.
    //
    
    #import "ViewController.h"
    #import "ReactiveObjC.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        [self customMethod];
    }
    
    - (void)customMethod {
        //1/创建信号
        RACSubject * subject = [RACSubject subject];
        //2/订阅信号
        [subject subscribeNext:^(id  _Nullable x) {
            NSLog(@"收到:%@", x);
        }];
        //3/发送信号
        [subject sendNext:@"妈妈说:溪浣双鲤,你该减肥了!"];
    }
    
    @end
    
    
    

    运行一下,能发现打印台打印出

    2019-06-16 11:26:05.201057+0800 RACSubject[3258:75710] 收到:妈妈说:溪浣双鲤,你该减肥了!
    
    

    分析以上三个步骤,我们能够发现:

    RACSubject的使用相比RACSignal来说更加简单,也更加灵活。

    下面我们先来分析RACSubject实现的整个流程,之后再对比RACSignal来仔细分析:

    1.首先来看看RACSubject类**

    @interface RACSubject<ValueType> : RACSignal<ValueType> <RACSubscriber>
    
    

    可以看出:

    RACSubject继承自RACSignal,也就是说RACSignal所有的API接口,在RACSubject里都能用。

    2.接下来分析RACSubject.m文件中的类:这段代码的理解注释我直接写在里面,方便理解

    #import "RACSubject.h"
    #import <ReactiveObjC/RACEXTScope.h>
    #import "RACCompoundDisposable.h"
    #import "RACPassthroughSubscriber.h"
    
    @interface RACSubject ()
    
    // Contains all current subscribers to the receiver.
    //
    // This should only be used while synchronized on `self`.
    @property (nonatomic, strong, readonly) NSMutableArray *subscribers;
    
    // Contains all of the receiver's subscriptions to other signals.
    @property (nonatomic, strong, readonly) RACCompoundDisposable *disposable;
    
    // Enumerates over each of the receiver's `subscribers` and invokes `block` for
    // each.
    - (void)enumerateSubscribersUsingBlock:(void (^)(id<RACSubscriber> subscriber))block;
    
    @end
    
    @implementation RACSubject
    
    #pragma mark Lifecycle
    
    //1、RACSubject生命周期
    //RACSubject初始化,提供一个类方法,并和RACSignal信号量一样,添加了一个销毁dispose,但是与RACSignal不同的是,额外初始化了一个_subscribers数组,这个数组的作用下面我会解释。
    //RACSubject销毁和RACSignal一样,都是在delloc的时候会调用dispose进行取消订阅并销毁。
    
    + (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;
    }
    
    - (void)dealloc {
        [self.disposable dispose];
    }
    
    #pragma mark Subscription
    //2/订阅信号的时候同样保存了subscriber代码块block,但是此处和RACSignal不同的是,是将代码块保存到了_subscribers数组中,一般使用数组来存储变量都不仅仅是存储,还便于统一管理,那我们我们先猜测是用数组来存储和管理这些subscriber代码块,至于怎么使用的,我们接着探索
    
    - (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;
    }
    
    //3/看到这个enum关键字,心里有种似曾相识的感jio,再看看下面的sendNext的enum以及error的enum,突然明白了RACSubject的管理机制
    
    - (void)enumerateSubscribersUsingBlock:(void (^)(id<RACSubscriber> subscriber))block {
        NSArray *subscribers;
        @synchronized (self.subscribers) {
            subscribers = [self.subscribers copy];
        }
    
        for (id<RACSubscriber> subscriber in subscribers) {
            block(subscriber);
        }
    }
    
    #pragma mark RACSubscriber
    //3/发送信号,RACSubject会遍历subscribers,并对其中保存的所有subscriber保存的代码块block,都调用发送信号方法sendNext,拿我们上面VC里面那个简单的例子进行分析就是:订阅了一次之后,RACSubject对象会把当前订阅保存的这个block保存到subscribers数组中,一旦RACSubject对象在外部调用了sendNext,就会遍历整个subscribers,对数组中保存的每一个subscriber订阅者发送sendNext信号进行信号量的sendNext调用,这样做达到的效果就是:只要订阅过一次,就会把当前这个subscriber订阅者代码块保存到一个数组中,在外面进行多次调用sendNext的时候,都会响应并调用这一个subscriber保存的代码块,达到了订阅者订阅一次,之后无论发送多少次都可以共用这一个订阅者,当然我们还可能想到订阅多次,然后在外面只发送一次信号,达到发送一次信号,然后多处订阅者代码块subscriber都响应的效果,原生的通知中心NSNotificationCenter就有这个类似的应用场景,不过相比这两种常用的应用场景,多次发送信号,添加多个订阅者这种场景基本很少用。所以我认为RACSubject主要是针对这两种场景进行使用的。
    
    - (void)sendNext:(id)value {
        [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
            [subscriber sendNext:value];
        }];
    }
    
    - (void)sendError:(NSError *)error {
        [self.disposable dispose];
        
        [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
            [subscriber sendError:error];
        }];
    }
    
    - (void)sendCompleted {
        [self.disposable dispose];
        
        [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
            [subscriber sendCompleted];
        }];
    }
    
    - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)d {
        if (d.disposed) return;
        [self.disposable addDisposable:d];
    
        @weakify(self, d);
        [d addDisposable:[RACDisposable disposableWithBlock:^{
            @strongify(self, d);
            [self.disposable removeDisposable:d];
        }]];
    }
    
    @end
    
    
    

    经过以上源文件的探索,我们可以确定的是:

    1、RACSubject是在初始化的时候创建了一个数组_subscribers来管理多个订阅者,收集多个订阅者,并且多个订阅者统一调用

    2、在外部使用RACSubject调用[RACSubject sendNext]的时候,会循环遍历_subscribers数组,让其中每个订阅者subscriber都调用信号量RACSignal的[RACSignal sendNext]方法,然后实现subscribeNext保存的block代码块

    3、需要注意的是,RACSubject和RACSignal相似的是,RACSubject在初始化的时候并没有保存代码块,而是保存了一个存放订阅者subscriber的数组,而这个订阅者和RACSignal的订阅者是一样的,都保存了代码块,RACSignal在订阅的时候会调用创建时的代码块,然后发送信号,发送信号又调用订阅时保存的代码块,所以最后执行订阅时保存的代码块。而RACSubject由于创建的时候没有保存代码块,只是在订阅的时候,订阅者自带了一个代码块,所以相当于RACSubject的_subscribers数组保存了一堆订阅者,同时每一个订阅者保存了一个代码块,不理解我这段话的的可以看我下面总结的思路图

    二、RACSubject和RACSignal的关系和区别


    废话不多说,先上图再分析总结:

    B397174A-DEAD-49BC-AFBA-4CF7D3DF9817.png

    从上面可以看出:

    RACSubject继承自RACSignal,但是一个RACSignal只能有一个订阅者,整个代码部分逻辑紧密相连不好拆分,书写起来相较RACSubject比较繁琐,而RACSubject利用数组管理订阅者,不仅使用起来更自由,更能实现多次发送消息,只“订阅一次”(至少看起来像只订阅一次),即达到了订阅一次,可以发送很多次,取得了多对一的效果,并且代码更好看。

    二、RACSubject设计思想总结


    相比较RACSignal,RACSubject类更面向实际业务开发,减少开发者的操作,并增加逻辑书写的自由度,并且解决了信号量的一次订阅,可以复用,多次发送使用的功能,当然也可以订阅很多次,然后发送一次,达到类似NSNotificationCenter全局通知的效果,非常nice!

    相关文章

      网友评论

        本文标题:探究ReactiveCocoa底层之RACSubject设计流程

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