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

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

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

事件總線

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

需求分析

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

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

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

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

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

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

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

基本流程就是訂閱者在DWEventBus上進(jìn)行訂閱抑淫,訂閱一個(gè)事件。發(fā)布者通過(guò)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≌饲拢基本代碼如下:

+(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)的訂閱。我們可以通過(guò)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ā)還沒(méi)有完成。當(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í)需求分析過(guò)后乞娄,思路都是很順其自然的瞬逊。大家多想多思考就都可以分析得到。

DWEventBus

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

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

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

DWEventBus

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末范删,一起剝皮案震驚了整個(gè)濱河市蕾域,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌到旦,老刑警劉巖旨巷,帶你破解...
    沈念sama閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異厢绝,居然都是意外死亡契沫,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門昔汉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)懈万,“玉大人,你說(shuō)我怎么就攤上這事靶病』嵬ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵娄周,是天一觀的道長(zhǎng)涕侈。 經(jīng)常有香客問(wèn)我,道長(zhǎng)煤辨,這世上最難降的妖魔是什么裳涛? 我笑而不...
    開封第一講書人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮众辨,結(jié)果婚禮上端三,老公的妹妹穿的比我還像新娘。我一直安慰自己鹃彻,他們只是感情好郊闯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般团赁。 火紅的嫁衣襯著肌膚如雪育拨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,610評(píng)論 1 305
  • 那天欢摄,我揣著相機(jī)與錄音熬丧,去河邊找鬼。 笑死剧浸,一個(gè)胖子當(dāng)著我的面吹牛锹引,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播唆香,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼吨艇!你這毒婦竟也來(lái)了躬它?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤东涡,失蹤者是張志新(化名)和其女友劉穎冯吓,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疮跑,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡组贺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祖娘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片失尖。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖渐苏,靈堂內(nèi)的尸體忽然破棺而出掀潮,到底是詐尸還是另有隱情,我是刑警寧澤琼富,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布仪吧,位于F島的核電站,受9級(jí)特大地震影響鞠眉,放射性物質(zhì)發(fā)生泄漏薯鼠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一械蹋、第九天 我趴在偏房一處隱蔽的房頂上張望出皇。 院中可真熱鬧,春花似錦朝蜘、人聲如沸恶迈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)暇仲。三九已至步做,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間奈附,已是汗流浹背全度。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留斥滤,地道東北人将鸵。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像佑颇,于是被迫代替她去往敵國(guó)和親顶掉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

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