前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >老司机出品———疯狂造轮子之事件总线的设计思路

老司机出品———疯狂造轮子之事件总线的设计思路

作者头像
老司机Wicky
发布2018-11-09 15:02:21
5460
发布2018-11-09 15:02:21
举报
文章被收录于专栏:老司机的简书

事件总线的设计思路

随着公司业务不断地迭代,数据层和UI层不断地下沉,被业务层进行包装,导致数据层想要跟UI层进行通信要经过一层层的带向上抛事件转发给对应的UI层。在重构过程中,我们希望设计一种通信方式,能直接连通数据层和UI层,而又不影响当前的业务层,在本次重构中,我们采取了事件总线的方式来解决这个问题。

事件总线

事件总线是对发布-订阅模式的一种实现。它是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,达到一种解耦的目的。

需求分析

设计之初,我们简单的分析了一下我们想要的功能:

  • 1.我们希望事件支持强弱类型的区分。因为造成此次重构的主要原因即为业务越来越复杂,业务层级随业务复杂度不断提升。我们希望一个事件具有一个强类型来代表某一个业务类型,一个弱类型枚举代表指定业务中某个特定事件。这样不同的业务事件实现了分类管理,逻辑更加清晰且后期维护方便,同时能更大程度上减少强类型事件的存在。
  • 2.我们希望当A广播给B一个事件,B处理完事件后,应该存在反馈机制,来告诉A我已将处理完事件了。
  • 3.我们希望尽可能的简化对外接口,可以实现随订阅者释放自动移除订阅关系的功能,从而更大程度的减少学习成本和避免野指针奔溃的问题。

基本结构

基于以上需求,老司机实现了一套发布者-订阅者模式的事件总线,基本结构如下:

基础结构

基本流程就是订阅者在DWEventBus上进行订阅,订阅一个事件。发布者通过DWEventBus发布一个事件后Bus自动续找对应的订阅者后进行回调。

代码实现

首先我们考虑到既然要实现随Target释放解除订阅关系,我们很自然的想到应该让订阅者Subscriber与Target建立某种关系,使其生命周期与Target相同,并且当Bus发布一个事件后,Subscriber应该做出响应。故Bus同时应于Subscriber建立持有关系。

此处我们考虑到不同总线间应该相对独立,互不干扰,所以我设计成Target对每一个Bus维护一个Subscriber。当订阅消息时,Bus持有Target对应自己的Subscriber。基本代码如下:

代码语言:javascript
复制
+(instancetype)subscriberWithTaget:(id)target bus:(DWEventBus *)bus {
    DWEventSubscriber * sub = objc_getAssociatedObject(target, [bus.uid UTF8String]);
    if (!sub) {
        sub = [DWEventSubscriber new];
        sub.target = target;
        sub.bus = bus;
        sub.proxy = [DWEventProxy proxyWithTarget:sub];
        objc_setAssociatedObject(target, [bus.uid UTF8String], sub, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return sub;
}


-(void)registEvent:(__kindof DWEvent *)event onSubscriber:(DWEventSubscriber *)sub entity:(DWEventEntity *)entity {
    ///在bus上注册
    NSMutableSet * eventSet = self.subscribersMap[event.eventName];
    if (!eventSet) {
        eventSet = [NSMutableSet set];
        [self.subscribersMap setValue:eventSet forKey:event.eventName];
    }
    [eventSet addObject:sub.proxy];
    ///Something else...
}

与Target进行关联是想让Subscriber的生命周期与相同。故Bus持有Subscriber不能直接进行强持有,添加Proxy代理层作为转发。

当Subscriber随着Target释放时,我们应该移除Bus上Subscriber对应的订阅。我们可以通过Subscriber自身对应的所有主事件类型去移除Bus中对应主类型Subscriber集合中的自身。

代码语言:javascript
复制
///移除bus中所有包含此sub的项
-(void)disposeHanlder {
    [self.eventsMap enumerateKeysAndObjectsUsingBlock:^(NSString * key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        NSMutableSet * subs = [self.bus.subscribersMap valueForKey:key];
        [subs removeObject:self.proxy];
    }];
}

-(void)dealloc {
    [self disposeHanlder];
}

至此我们解决了自动移除订阅关系,但是对应的事件分发还没有完成。当消息分发给Subscriber后,应由Subscriber根据事件的强弱类型进行分发。故注册订阅时,在Subscriber上同时应该注册详细的事件关系。

代码语言:javascript
复制
-(void)registEvent:(__kindof DWEvent *)event onSubscriber:(DWEventSubscriber *)sub entity:(DWEventEntity *)entity {
    ///Do something else...
    ///在subscriber上注册
    ///取出一级map
    NSMutableDictionary * subTypeMD = [sub.eventsMap valueForKey:event.eventName];
    if (!subTypeMD) {
        subTypeMD = [NSMutableDictionary dictionary];
        [sub.eventsMap setValue:subTypeMD forKey:event.eventName];
    }
    ///取出二级set
    NSMutableSet * entitys = [subTypeMD valueForKey:@(event.subType).stringValue];
    if (!entitys) {
        entitys = [NSMutableSet set];
        [subTypeMD setValue:entitys forKey:@(event.subType).stringValue];
    }
    
    ///添加观察者
    [entitys addObject:entity];
}

这样,一个entity负责处理一个事件订阅关系,当Bus发送事件后,Subscriber按照事件类型转发给对应的entity即可。

代码语言:javascript
复制
-(void)receiveEvent:(__kindof DWEvent *)event {
    dispatch_semaphore_wait(self.sema, DISPATCH_TIME_FOREVER);
    NSDictionary * subType = [self.eventsMap valueForKey:event.eventName];
    NSSet * entitys = [subType valueForKey:@(event.subType).stringValue];
    [entitys enumerateObjectsUsingBlock:^(DWEventEntity * _Nonnull obj, BOOL * _Nonnull stop) {
        [obj receiveEvent:event target:self.target];
    }];
    dispatch_semaphore_signal(self.sema);
}

至此我们就完成了事件的订阅和发布。事件移除只要按照对应的层级关系移除Bus及Subscriber上对应的entity就好,此处不做赘述。

基于需求我们大致粗略的完成了一个事件总线,借助他我们就可以完成代码结构建的解耦及消息互通。其实需求分析过后,思路都是很顺其自然的。大家多想多思考就都可以分析得到。

DWEventBus

DWEventBus即是本次重构我设计的一个事件总线。他大概具备以下功能:

  • 发布-订阅模式
  • 联合事件
  • 指定发布和订阅回调所在队列
  • 订阅方执行完毕的反馈

具体可以去我的GitHub看一下,如果使用过程中有什么问题大家可以随时给我提Issue或者给我留言。

相关参考资料:

实现一个优雅的iOS消息总线

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018.10.23 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 事件总线
  • 需求分析
  • 基本结构
  • 代码实现
  • DWEventBus
相关产品与服务
事件总线
腾讯云事件总线(EventBridge)是一款安全,稳定,高效的云上事件连接器,作为流数据和事件的自动收集、处理、分发管道,通过可视化的配置,实现事件源(例如:Kafka,审计,数据库等)和目标对象(例如:CLS,SCF等)的快速连接,当前 EventBridge 已接入 100+ 云上服务,助力分布式事件驱动架构的快速构建。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档