QTEventBus

前言

最近看了下QTEventBus的源码,防止遗忘,写下来巩固一下。水平不太行,看的也比较浅
另外本文不太设计QTEventBus中的消息(其实是我没看)

使用

QTEventBus使用比较简单,主要是QTEventBus.h中的方法

  1. 添加监听
//监听全局总线,监听的生命周期和object一样
#define QTSub(_object_,_className_) ((QTEventSubscriberMaker<_className_ *> *)[_object_ subscribeSharedBus:[_className_ class]])
#define QTSubName(_object_,_name_) ([_object_ subscribeSharedBusOfName:_name_])
//监听全局总线,异步在主线程监听
#define QTSubMain(_object_,_className_) ([QTSub(_object_, _className_) atQueue:dispatch_get_main_queue()])
//全局总线监听NSNotification
#define QTSubNoti(_object_,_name_) ((QTEventSubscriberMaker<NSNotification *> *)[_object_ subscribeNotification:_name_])
//全局总线监听QTJsonEvent
#define QTSubJSON(_object_,_name_) ((QTEventSubscriberMaker<QTJsonEvent *> *)[_object_ subscribeSharedBusOfJSON:_name_])
  1. 发布消息
/**
 发布Event,等待event执行结束
 */
- (void)dispatch:(id<QTEvent>)event;

/**
 异步到eventbus内部queue上dispath
 */
- (void)dispatchOnBusQueue:(id<QTEvent>)event;

/**
 异步到主线程dispatch
 */
- (void)dispatchOnMain:(id<QTEvent>)event;

订阅

订阅的宏定义中,除了通知之外,作者一共提供了4个宏定义。而实际上这四个宏定义中,QTSub是基础,其他3个都是在此基础上的补充。首先我们看一下QTSub都做了些什么。
QTSub()实际上就是创建监听者(可以这么理解,但是这一步创建的并非最终的监听者),我们先看一下QTEventSubscriberMaker

/**
 内存中保存的监听者
 */
@interface QTEventSubscriberMaker()

- (instancetype)initWithEventBus:(QTEventBus *)eventBus
                      eventClass:(Class)eventClass;

@property (strong, nonatomic) Class eventClass;

@property (strong, nonatomic) NSObject * lifeTimeTracker;

@property (strong, nonatomic) dispatch_queue_t queue;

@property (strong, nonatomic) NSMutableArray * eventSubTypes;

@property (strong, nonatomic) QTEventBus * eventBus;

@property (copy, nonatomic) void(^hander)(__kindof NSObject *);

@end

我们逐一看一下QTEventSubscriberMaker的属性

  • eventClass

这是类名,需要被监听对象的类型
因为是类名为关键字,这就导致了子类的通知父类是无法接收到的

  • lifeTimeTracker

这是自动释放监听者的工具

  • queue

监听回调的线程。默认为空

  • eventSubTypes

这是一个二级事件。后续会讲

  • eventBus

单例。管理监听者,发布通知

  • hander

回调

创建监听者

这里可能有点歧义,只是为了方便理解,这一步创建的并非最终的监听者,只是一个临时变量
OK,我们看回到QTSub(),我们可以看到,QTSub()实际上是调用这样一个方法

/**
 在EventBus单例shared上监听指定类型的事件,并且跟随self一起取消监听
 */
- (QTEventSubscriberMaker *)subscribeSharedBus:(Class)eventClass{
    return [QTEventBus shared].on(eventClass).freeWith(self);
}

继续看一下on(eventClass),这里是block的链式编程,这块我不太熟,就不展开了,后续继续学习

- (QTEventSubscriberMaker<id> *(^)(Class eventClass))on{
    return ^QTEventSubscriberMaker *(Class eventClass){
        return [[QTEventSubscriberMaker alloc] initWithEventBus:self
                                                     eventClass:eventClass];
    };
}

可以看到,在该方法中一共两个参数。self和eventClass
首先该方法是EventBus的实例方法, 所以这里的self就是指EventBus,我们还可以看到,EventBus实际上是一个单例

+ (instancetype)shared{
    static QTEventBus * _instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[QTEventBus alloc] init];
    });
    return _instance;
}

另外一个就是类型。我们暂时先不管保存类型是干什么用的。到这里,监听者就创建好了。

监听者完善

虽然到这一步监听已经创建好了,不过在 QTEventSubscriberMaker 还有其他几个方法我们可以看一下

- (QTEventSubscriberMaker *)atQueue:(dispatch_queue_t)queue{
    return self.atQueue(queue);
}

调用atQueue就是使我们的回调block在我们制定的队列去执行

- (QTEventSubscriberMaker *)freeWith:(id)object{
    return self.freeWith(object);
}

自动释放,后面讲

- (QTEventSubscriberMaker *)ofSubType:(NSString *)eventType{
    return self.ofSubType(eventType);
}

添加二级事件,后面讲

- (id<QTEventToken>)next:(QTEventNextBlock)hander{
    return self.next(hander);
}

保存block回调和监听者,下面讲

保存监听者

既然创建好了监听者,第二步肯定就是保存了。QTEventBus是如何保存监听者的呢,我们看一下作者的示例

[QTSub(self, DemoEvent) next:^(DemoEvent *event) {
        NSLog(@"%ld",event.count);
}];

我们可以看到,紧着跟QTSub的是next。而保存监听者实际上就是在这一步中做的

- (id<QTEventToken>(^)(void(^)(id event)))next{
    return ^id<QTEventToken>(void(^hander)(__kindof NSObject * event)){
        self.hander = hander;
        return [self.eventBus _createNewSubscriber:self];
    };
}

先保存了block。然后调用了_createNewSubscriber

- (id<QTEventToken>)_createNewSubscriber:(QTEventSubscriberMaker *)maker{
    if (!maker.hander) {
        return nil;
    }
    if (maker.eventSubTypes.count == 0) {//一级事件
        _QTEventToken * token = [self _addSubscriberWithMaker:maker eventType:nil];
        return token;
    }
    NSMutableArray * tokens = [[NSMutableArray alloc] init];
    for (NSString * eventType in maker.eventSubTypes) {
        _QTEventToken * token = [self _addSubscriberWithMaker:maker eventType:eventType];
        [tokens addObject:token];
    }
    _QTComposeToken * token = [[_QTComposeToken alloc] initWithTokens:tokens];
    return token;
}

我们先不考虑二级事件。先说一级事件。我们继续看一下_addSubscriberWithMaker方法

- (_QTEventToken *)_addSubscriberWithMaker:(QTEventSubscriberMaker *)maker eventType:(NSString *)eventType{
    __weak typeof(self) weakSelf = self;
    NSString * eventKey = __generateUnqiueKey(maker.eventClass, eventType);
    NSString * groupId = [self.prefix stringByAppendingString:eventKey];
    NSString * uniqueId = [groupId stringByAppendingString:@([NSDate date].timeIntervalSince1970).stringValue];
    _QTEventToken * token = [[_QTEventToken alloc] initWithKey:uniqueId];
    BOOL isCFNotifiction = (maker.eventClass == [NSNotification class]);
    if (eventType && isCFNotifiction) {
        [self _addNotificationObserverIfNeeded:eventType];
    }
    token.onDispose = ^(NSString *uniqueId) {
        __strong typeof(self) strongSelf = weakSelf;
        if (!strongSelf) {
            return;
        }
        BOOL empty = [strongSelf.collection removeUniqueId:uniqueId ofKey:groupId];
        if (empty && isCFNotifiction) {
            [strongSelf _removeNotificationObserver:eventType];
        }
    };
    //创建监听者
    _QTEventSubscriber * subscriber = [[_QTEventSubscriber alloc] init];
    subscriber.queue = maker.queue;
    subscriber.handler = maker.hander;
    subscriber.uniqueId = uniqueId;
    if (maker.lifeTimeTracker) {
        [maker.lifeTimeTracker.eb_disposeBag addToken:token];
    }
    [self.collection addObject:subscriber forKey:groupId];
    return token;
}

看着很复杂的样子,其实不复杂,一步一步看

创建关键字

__generateUnqiueKey字面上的意思就是创建一个唯一标识符

static inline NSString * __generateUnqiueKey(Class<QTEvent> cls,NSString * eventType){
    Class targetClass = [cls respondsToSelector:@selector(eventClass)] ? [cls eventClass] : cls;
    if (eventType) {
        return [NSString stringWithFormat:@"%@_of_%@",eventType,NSStringFromClass(targetClass)];
    }else{
        return NSStringFromClass(targetClass);
    }
}

看完源码之后,确实如此。不过这里有一个细节就是先判断一下需要监听的类是否实现了eventClass方法,如果是则使用该方法的返回值而非类名。这个主要是防止用户使用NSNotification和NSString的子类,然后订阅之后接收不到消息。看一下作者在NSString和NSNotification的分类中做了什么

@implementation NSString (QTEevnt)
- (NSString *)eventSubType{
    return [self copy];
}

+ (Class)eventClass{
    return [NSString class];
}
@end
@implementation NSNotification (QTEvent)

+ (Class)eventClass{
    return [NSNotification class];
}

- (NSString *)eventSubType{
    return self.name;
}
@end

同样的。我们自定义的类,也可以在根类中实现eventClass,从而保证父类以及子类都能接收到通知
做完这一步就是根据类名创建标识符了。eventType是二级事件。虽然先不考虑,但是在这里我们还是需要了解一下,一级事件就是以类名作为关键字,而二级事件是eventType_of_类名
我们可以看到,上一步创建的是eventKey,作者根据eventKey创建了grounpId,和uniqueId

groupId = 时间 + eventKey
uniqueId = groupId + 时间

然后根据uniqueId创建了一个Token(用于取消监听)
再然后就是个根据Maker创建监听者并保存。这里还有两个细节

  • lifeTimeTracker

自动释放监听者,最后再说,先不看
仅需要了解在这一步创建了一个token,添加到了eb_disposeBags属性中

  • QTEventBusCollection

我们可以看到,作者是将监听者添加到这样一个collection中了,这里就不展开讲了
collection其实就是一个双向链表
根据groupId创建一个链表,若存在该链表就继续往后添加。其实就是将同一个类的一级监听或同一个二级监听放在一个链表上
再将链表保存到一个字典中,groupid作为关键字

到这里呢,监听者就创建并保存好了,下一步就是发布消息了

发消息

作者一共提供了3中发布消息的方式,我们主要看第一个方法
我们详细看一下第一个方法

- (void)dispatch:(id<QTEvent>)event{
    if (!event) {
        return;
    }
    NSString * eventSubType = [event respondsToSelector:@selector(eventSubType)] ? [event eventSubType] : nil;
    if (eventSubType) {
        //二级事件
        NSString * key = __generateUnqiueKey(event.class, eventSubType);
        [self _publishKey:key event:event];
    }
    //一级事件
    NSString * key = __generateUnqiueKey(event.class, nil);
    [self _publishKey:key event:event];
}

首先是判断是否需要发送二级事件,若有则发送,若无则跳过。二级事件我们暂先不考虑。
然后就是发送一级事件。继续看源码

- (void)_publishKey:(NSString *)eventKey event:(NSObject *)event{
    NSString * groupId = [self.prefix stringByAppendingString:eventKey];
    NSArray * subscribers = [self.collection objectsForKey:groupId];
    if (!subscribers || subscribers.count == 0) {
        return;
    }
    for (_QTEventSubscriber * subscriber in subscribers) {
        if (subscriber.queue) { //异步分发
            dispatch_async(subscriber.queue, ^{
                if (subscriber.handler) {
                    subscriber.handler(event);
                }
            });
        }else{ //同步分发
            if (subscriber.handler) {
                subscriber.handler(event);
            }
        }

    }
}

首先根据groupId取出所有一级事件的监听者,然后遍历调用。
到这里,就完成整个流程了。

发送消息扩展

- (void)dispatchOnBusQueue:(id<QTEvent>)event{
    dispatch_async(self.publishQueue, ^{
        [self dispatch:event];
    });
}

- (void)dispatchOnMain:(id<QTEvent>)event{
    if ([NSThread isMainThread]) {
        [self dispatch:event];
    }else{
        dispatch_async(dispatch_get_main_queue(), ^{
            [self dispatch:event];
        });
    }
}

可以看到这两个方法一个是在作者定义的队列上发送消息,另外一个实在主线程执行。
不过要特别注意的是,如果在创建监听者时指定了队列,那么这边的设置将不起作用

   if (subscriber.queue) { //异步分发
      dispatch_async(subscriber.queue, ^{
        if (subscriber.handler) 
          subscriber.handler(event); 
        }
      });
   }

可以看到最终还是在制定的队列上执行回调

二级事件

前面我们一直没有说到二级事件,那么我们现在可以说一说二级事件了。
我们再看一下QTEventSubscriberMaker的方法,可以看到有这样一个方法

- (QTEventSubscriberMaker<id> *(^)(NSString *))ofSubType{
    return ^QTEventSubscriberMaker *(NSString * eventType){
        if (!eventType) {
            return self;
        }
        @synchronized(self) {
            [self.eventSubTypes addObject:eventType];
        }
        return self;
    };
}

即调用ofSubType就是添加key值到数组中。然后在_createNewSubscriber方法中,会逐一将这些二级事件保存到collection中去。

NSMutableArray * tokens = [[NSMutableArray alloc] init];
    for (NSString * eventType in maker.eventSubTypes) {
        _QTEventToken * token = [self _addSubscriberWithMaker:maker eventType:eventType];
        [tokens addObject:token];
    }
    _QTComposeToken * token = [[_QTComposeToken alloc] initWithTokens:tokens];

最后在dispatch时,根据参数中的eventSubType的来确定是否要发送二级事件以及发送哪个二级事件。

NSString * eventSubType = [event respondsToSelector:@selector(eventSubType)] ? [event eventSubType] : nil;
    if (eventSubType) {
        //二级事件
        NSString * key = __generateUnqiueKey(event.class, eventSubType);
        [self _publishKey:key event:event];
    }

值得注意的是:

  • 即使发送了二级事件,还是会再发送一次一级事件。
  • 如果调用了ofSubType方法,那么此处的订阅就是一个二级事件了,不再会触发一级事件。

自动释放

最后我们就来看看QTEventBus是如何做到自动释放的,以subscribeSharedBus为例,其他方法都一样

- (QTEventSubscriberMaker *)subscribeSharedBus:(Class)eventClass{
    return [QTEventBus shared].on(eventClass).freeWith(self);
}

我们可以注意到,最后调用了一个freeeWith(self),继续看源码

- (QTEventSubscriberMaker<id> *(^)(id))freeWith{
    return ^QTEventSubscriberMaker *(id lifeTimeTracker){
        self.lifeTimeTracker = lifeTimeTracker;
        return self;
    };
}

即将监听者赋值给maker,我们再后头看看保存监听者时做了什么

    _QTEventToken * token = [[_QTEventToken alloc] initWithKey:uniqueId];
    ...
//创建监听者
    _QTEventSubscriber * subscriber = [[_QTEventSubscriber alloc] init];
    subscriber.queue = maker.queue;
    subscriber.handler = maker.hander;
    subscriber.uniqueId = uniqueId;
    if (maker.lifeTimeTracker) {
        [maker.lifeTimeTracker.eb_disposeBag addToken:token];
    }

在监听者中添加了token.那我们就看一下eb_disposeBag做了什么

- (NSMutableArray<id<QTEventToken>> *)tokens{
    if (!_tokens) {
        _tokens = [[NSMutableArray alloc] init];
    }
    return _tokens;
}

- (void)addToken:(id<QTEventToken>)token{
    @synchronized(self) {
        [self.tokens addObject:token];
    }
}

- (void)dealloc{
    @synchronized(self) {
        for (id<QTEventToken> token in self.tokens) {
            if ([token respondsToSelector:@selector(dispose)]) {
                [token dispose];
            }
        }
    }
}

可以看到监听者保存了所有的一级事件和二级事件的token,当监听者被释放时,调用了token的dispose方法

- (void)dispose{
    @synchronized(self){
        if (_isDisposed) {
            return;
        }
        _isDisposed = YES;
    }
    if (self.onDispose) {
        self.onDispose(self.uniqueId);
    }
}

调用了onDispose。OK,继续回头看在保存监听者时做了什么

    token.onDispose = ^(NSString *uniqueId) {
        __strong typeof(self) strongSelf = weakSelf;
        if (!strongSelf) {
            return;
        }
        BOOL empty = [strongSelf.collection removeUniqueId:uniqueId ofKey:groupId];
        if (empty && isCFNotifiction) {
            [strongSelf _removeNotificationObserver:eventType];
        }
    };

可以看到最终从collection中移除了该事件的监听

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

推荐阅读更多精彩内容

  • runtime 和 runloop 作为一个程序员进阶是必须的,也是非常重要的, 在面试过程中是经常会被问到的, ...
    SOI阅读 21,774评论 3 63
  • runtime 和 runloop 作为一个程序员进阶是必须的,也是非常重要的, 在面试过程中是经常会被问到的, ...
    made_China阅读 1,200评论 0 7
  • 简介 QTEventBus 事件总线集中管理事件流,跟ReactiveCocoa函数响应式编程框架类似(Funct...
    sankun阅读 2,754评论 0 1
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,680评论 2 59
  • 前言 在微服务架构的系统中,我们通常会使用轻量级的消息代理来构建一个共用的消息主题让系统中所有微服务实例都连接上来...
    Chandler_珏瑜阅读 6,555评论 2 39