鸡汤前言:
我们先养成习惯,然后习惯再养成我们!
有些事不是看到了希望才去坚持,而是因为坚持而看到了希望!
直接上今天的干货部分,来深入了解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!
网友评论