首页 >iOS开发

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

2018-11-07 09:35 编辑: suiling 分类:iOS开发 来源:老司机Wicky

事件总线的设计思路

随着公司业务不断地迭代,数据层和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。基本代码如下:

+(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集合中的自身。

///移除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上同时应该注册详细的事件关系。

-(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即可。

-(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或者给我留言。

DWEventBus

相关参考资料:

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

作者:老司机Wicky

链接:https://www.jianshu.com/p/41016504e7ff

搜索CocoaChina微信公众号:CocoaChina
微信扫一扫
订阅每日移动开发及APP推广热点资讯
公众号:
CocoaChina
我要投稿   收藏文章
上一篇:iOS 实现步骤进度条
下一篇:RunLoop入门学习补充资料

相关资讯

我来说两句
发表评论
您还没有登录!请登录注册
所有评论(0

综合评论

相关帖子

sina weixin mail 回到顶部