隨著公司業(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)如下:
基本流程就是訂閱者在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或者給我留言。
相關(guān)參考資料: