iOS开发技能站———疯狂造轮子之事件总线的设计思路(新)

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

事件总线

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

需求分析

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

  • 1.我们希望事件支持强弱类型的区分。因为造成此次重构的主要原因即为业务越来越复杂,业务层级随业务复杂度不断提升。我们希望一个事件具有一个强类型来代表某一个业务类型,一个弱类型枚举代表指定业务中某个特定事件。这样不同的业务事件实现了分类管理,逻辑更加清晰且后期维护方便,同时能更大程度上减少强类型事件的存在。

  • 2.我们希望当A广播给B一个事件,B处理完事件后,应该存在反馈机制,来告诉A我已将处理完事件了。

  • 3.我们希望尽可能的简化对外接口,可以实现随订阅者释放自动移除订阅关系的功能,从而更大程度的减少学习成本和避免野指针奔溃的问题。

基本结构

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

image

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

感谢观看赠资料:

对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!
iOS交流裙:1001906160,详细整理在群文件可以见;

——点击加入:iOS技术分享

image

代码实现

首先我们考虑到既然要实现随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

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

推荐阅读更多精彩内容