老司機(jī)出品———瘋狂造輪子之事件總線的設(shè)計(jì)思路

事件總線的設(shè)計(jì)思路

隨著公司業(yè)務(wù)不斷地迭代,數(shù)據(jù)層和UI層不斷地下沉,被業(yè)務(wù)層進(jìn)行包裝,導(dǎo)致數(shù)據(jù)層想要跟UI層進(jìn)行通信要經(jīng)過一層層的帶向上拋事件轉(zhuǎn)發(fā)給對(duì)應(yīng)的UI層。在重構(gòu)過程中,我們希望設(shè)計(jì)一種通信方式,能直接連通數(shù)據(jù)層和UI層,而又不影響當(dāng)前的業(yè)務(wù)層,在本次重構(gòu)中,我們采取了事件總線的方式來解決這個(gè)問題。

事件總線

事件總線是對(duì)發(fā)布-訂閱模式的一種實(shí)現(xiàn)。它是一種集中式事件處理機(jī)制,允許不同的組件之間進(jìn)行彼此通信而又不需要相互依賴,達(dá)到一種解耦的目的。

需求分析

設(shè)計(jì)之初,我們簡(jiǎn)單的分析了一下我們想要的功能:

  • 1.我們希望事件支持強(qiáng)弱類型的區(qū)分。因?yàn)樵斐纱舜沃貥?gòu)的主要原因即為業(yè)務(wù)越來越復(fù)雜,業(yè)務(wù)層級(jí)隨業(yè)務(wù)復(fù)雜度不斷提升。我們希望一個(gè)事件具有一個(gè)強(qiáng)類型來代表某一個(gè)業(yè)務(wù)類型,一個(gè)弱類型枚舉代表指定業(yè)務(wù)中某個(gè)特定事件。這樣不同的業(yè)務(wù)事件實(shí)現(xiàn)了分類管理,邏輯更加清晰且后期維護(hù)方便,同時(shí)能更大程度上減少強(qiáng)類型事件的存在。

  • 2.我們希望當(dāng)A廣播給B一個(gè)事件,B處理完事件后,應(yīng)該存在反饋機(jī)制,來告訴A我已將處理完事件了。

  • 3.我們希望盡可能的簡(jiǎn)化對(duì)外接口,可以實(shí)現(xiàn)隨訂閱者釋放自動(dòng)移除訂閱關(guān)系的功能,從而更大程度的減少學(xué)習(xí)成本和避免野指針奔潰的問題。

基本結(jié)構(gòu)

基于以上需求,老司機(jī)實(shí)現(xiàn)了一套發(fā)布者-訂閱者模式的事件總線,基本結(jié)構(gòu)如下:

基礎(chǔ)結(jié)構(gòu)

基本流程就是訂閱者在DWEventBus上進(jìn)行訂閱,訂閱一個(gè)事件。發(fā)布者通過DWEventBus發(fā)布一個(gè)事件后Bus自動(dòng)續(xù)找對(duì)應(yīng)的訂閱者后進(jìn)行回調(diào)。

代碼實(shí)現(xiàn)

首先我們考慮到既然要實(shí)現(xiàn)隨Target釋放解除訂閱關(guān)系,我們很自然的想到應(yīng)該讓訂閱者Subscriber與Target建立某種關(guān)系,使其生命周期與Target相同,并且當(dāng)Bus發(fā)布一個(gè)事件后,Subscriber應(yīng)該做出響應(yīng)。故Bus同時(shí)應(yīng)于Subscriber建立持有關(guān)系。

此處我們考慮到不同總線間應(yīng)該相對(duì)獨(dú)立,互不干擾,所以我設(shè)計(jì)成Target對(duì)每一個(gè)Bus維護(hù)一個(gè)Subscriber。當(dāng)訂閱消息時(shí),Bus持有Target對(duì)應(yīng)自己的Subscriber?;敬a如下:

+(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上注冊(cè)
    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進(jìn)行關(guān)聯(lián)是想讓Subscriber的生命周期與相同。故Bus持有Subscriber不能直接進(jìn)行強(qiáng)持有,添加Proxy代理層作為轉(zhuǎn)發(fā)。

當(dāng)Subscriber隨著Target釋放時(shí),我們應(yīng)該移除Bus上Subscriber對(duì)應(yīng)的訂閱。我們可以通過Subscriber自身對(duì)應(yīng)的所有主事件類型去移除Bus中對(duì)應(yīng)主類型Subscriber集合中的自身。

///移除bus中所有包含此sub的項(xiàng)
-(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];
}

至此我們解決了自動(dòng)移除訂閱關(guān)系,但是對(duì)應(yīng)的事件分發(fā)還沒有完成。當(dāng)消息分發(fā)給Subscriber后,應(yīng)由Subscriber根據(jù)事件的強(qiáng)弱類型進(jìn)行分發(fā)。故注冊(cè)訂閱時(shí),在Subscriber上同時(shí)應(yīng)該注冊(cè)詳細(xì)的事件關(guān)系。

-(void)registEvent:(__kindof DWEvent *)event onSubscriber:(DWEventSubscriber *)sub entity:(DWEventEntity *)entity {
    ///Do something else...
    ///在subscriber上注冊(cè)
    ///取出一級(jí)map
    NSMutableDictionary * subTypeMD = [sub.eventsMap valueForKey:event.eventName];
    if (!subTypeMD) {
        subTypeMD = [NSMutableDictionary dictionary];
        [sub.eventsMap setValue:subTypeMD forKey:event.eventName];
    }
    ///取出二級(jí)set
    NSMutableSet * entitys = [subTypeMD valueForKey:@(event.subType).stringValue];
    if (!entitys) {
        entitys = [NSMutableSet set];
        [subTypeMD setValue:entitys forKey:@(event.subType).stringValue];
    }
    
    ///添加觀察者
    [entitys addObject:entity];
}

這樣,一個(gè)entity負(fù)責(zé)處理一個(gè)事件訂閱關(guān)系,當(dāng)Bus發(fā)送事件后,Subscriber按照事件類型轉(zhuǎn)發(fā)給對(duì)應(yīng)的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);
}

至此我們就完成了事件的訂閱和發(fā)布。事件移除只要按照對(duì)應(yīng)的層級(jí)關(guān)系移除Bus及Subscriber上對(duì)應(yīng)的entity就好,此處不做贅述。

基于需求我們大致粗略的完成了一個(gè)事件總線,借助他我們就可以完成代碼結(jié)構(gòu)建的解耦及消息互通。其實(shí)需求分析過后,思路都是很順其自然的。大家多想多思考就都可以分析得到。

DWEventBus

DWEventBus即是本次重構(gòu)我設(shè)計(jì)的一個(gè)事件總線。他大概具備以下功能:

  • 發(fā)布-訂閱模式
  • 聯(lián)合事件
  • 指定發(fā)布和訂閱回調(diào)所在隊(duì)列
  • 訂閱方執(zhí)行完畢的反饋

具體可以去我的GitHub看一下,如果使用過程中有什么問題大家可以隨時(shí)給我提Issue或者給我留言。

DWEventBus

相關(guān)參考資料:

實(shí)現(xiàn)一個(gè)優(yōu)雅的iOS消息總線

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,500評(píng)論 19 139
  • 項(xiàng)目到了一定階段會(huì)出現(xiàn)一種甜蜜的負(fù)擔(dān):業(yè)務(wù)的不斷發(fā)展與人員的流動(dòng)性越來越大,代碼維護(hù)與測(cè)試回歸流程越來越繁瑣。這個(gè)...
    fdacc6a1e764閱讀 3,329評(píng)論 0 6
  • 前言 在微服務(wù)架構(gòu)的系統(tǒng)中,我們通常會(huì)使用輕量級(jí)的消息代理來構(gòu)建一個(gè)共用的消息主題讓系統(tǒng)中所有微服務(wù)實(shí)例都連接上來...
    Chandler_玨瑜閱讀 6,763評(píng)論 2 39
  • Java 注解(Annotation)又稱之為 Java 標(biāo)注、元數(shù)據(jù),是 Java 1.5 之后加入的一種特殊語...
    躬行之閱讀 331評(píng)論 0 1
  • 起因 聽了一場(chǎng)知乎live 前端工程師的入門與進(jìn)階 作者水平比較高,查了之后才發(fā)現(xiàn)比想象的還要高,大牛級(jí)別。 好的...
    任我笑笑閱讀 296評(píng)論 0 0

友情鏈接更多精彩內(nèi)容