探究ReactiveCocoa底层之RACSubject设计流程

鸡汤前言:
我们先养成习惯,然后习惯再养成我们!
有些事不是看到了希望才去坚持,而是因为坚持而看到了希望!

直接上今天的干货部分,来深入了解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 : RACSignal 

可以看出:

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

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

#import "RACSubject.h"
#import 
#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 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)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 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 subscriber))block {
    NSArray *subscribers;
    @synchronized (self.subscribers) {
        subscribers = [self.subscribers copy];
    }

    for (id 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 subscriber) {
        [subscriber sendNext:value];
    }];
}

- (void)sendError:(NSError *)error {
    [self.disposable dispose];
    
    [self enumerateSubscribersUsingBlock:^(id subscriber) {
        [subscriber sendError:error];
    }];
}

- (void)sendCompleted {
    [self.disposable dispose];
    
    [self enumerateSubscribersUsingBlock:^(id 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的关系和区别


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

探究ReactiveCocoa底层之RACSubject设计流程_第1张图片
B397174A-DEAD-49BC-AFBA-4CF7D3DF9817.png

从上面可以看出:

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

二、RACSubject设计思想总结


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

溪浣双鲤的技术摸爬滚打之路

你可能感兴趣的:(探究ReactiveCocoa底层之RACSubject设计流程)