BC架構(gòu)探索之路
做iOS也有些年頭了妻顶,最近把項(xiàng)目核心模塊的架構(gòu)重新設(shè)計(jì)了一番,這里做一些記錄匈子。
首先河胎,我們要對(duì)基礎(chǔ)的設(shè)計(jì)模式有一定的認(rèn)知。這些基礎(chǔ)的設(shè)計(jì)模式虎敦,便是MVC游岳、MVVM政敢、VIPER。
MVC胚迫、MVVM
關(guān)于 MVC 喷户,斯坦福的 Paul 老頭有一張經(jīng)典的圖示,相信大部分iOSer都看過(guò):
當(dāng)有多個(gè)模塊時(shí)访锻,我們需要有多個(gè) MVC 互相配合:
可以看到褪尝,多個(gè)模塊之間的交互都是通過(guò) Controller 層。以上就是 MVC 的概覽期犬,那么 MVVM 是什么樣的呢河哑?
MVVM 是 Model-View-ViewModel 的縮寫(xiě)。其實(shí)在 MVC 的基礎(chǔ)上再稍進(jìn)一步龟虎,把 Controller 與 View 之間的數(shù)據(jù)傳遞過(guò)程獨(dú)立出來(lái)璃谨,封裝成一個(gè)模塊,叫做 ViewModel 鲤妥,這就成了 MVVM 了佳吞。在 MVVM 的基礎(chǔ)上,通常還會(huì)使用雙向綁定技術(shù)棉安,使得 View 和 ViewModel 之間可以自動(dòng)同步底扳。
VIPER
VIPER ,全稱 View-Interactor-Presenter-Entity-Router 垂券。這是另一種細(xì)分 MVC 而得到的架構(gòu)。從上圖可以看到羡滑, VIPER 實(shí)際上是將 MVC 中的 Controller 細(xì)化為了三個(gè)模塊菇爪,即 Presenter、Interactor柒昏、Router 凳宙。 Entity 負(fù)責(zé)數(shù)據(jù)持久化, Interactor 負(fù)責(zé)業(yè)務(wù)相關(guān)的邏輯計(jì)算等职祷, Presenter 則負(fù)責(zé)將業(yè)務(wù)數(shù)據(jù)傳遞給 View 氏涩,也負(fù)責(zé)處理 View 的事件。大部分 View 的事件是交由邏輯側(cè) interactor 處理有梆,在 interactor 處理完后會(huì)觸發(fā)必要的 UI 刷新是尖。跳轉(zhuǎn)相關(guān)的 View 事件則交由 Router 處理。
可以看到泥耀, VIPER 和 MVVM 并不矛盾饺汹,我們可以在 MVVM 的基礎(chǔ)上繼續(xù)細(xì)化得到 VIPER , ViewModel 相關(guān)的邏輯放在 Presenter 中即可痰催。
同樣兜辞,當(dāng)有多個(gè)模塊時(shí)迎瞧,我們需要有多個(gè) VIPER 互相配合。
縱覽
可以看到傳統(tǒng)架構(gòu)的進(jìn)化過(guò)程: MVC -> MVVM -> VIPER 逸吵。這是一個(gè)對(duì)架構(gòu)不斷細(xì)化的過(guò)程凶硅。在工程實(shí)踐中,我們的業(yè)務(wù)采用什么架構(gòu)扫皱,需要根據(jù)業(yè)務(wù)的形態(tài)和頻繁變動(dòng)的模塊而定足绅。
不知大家有沒(méi)有發(fā)現(xiàn),以上所述的架構(gòu)解決的是單個(gè)業(yè)務(wù)模塊內(nèi)的職責(zé)劃分問(wèn)題啸罢,并沒(méi)有解決如何將多個(gè)業(yè)務(wù)模塊組合在一起的問(wèn)題编检。即多個(gè) MVC 或者 多個(gè) VIPER 之間如何配合?實(shí)踐中我們發(fā)現(xiàn):
- 通過(guò)對(duì) MVC 的進(jìn)一步細(xì)分扰才,可以從單個(gè)業(yè)務(wù)模塊的角度上緩解 MVC 中 Controller 中心化所導(dǎo)致的 massive view controller 的問(wèn)題允懂,但對(duì)于有眾多業(yè)務(wù)模塊的 Controller 來(lái)說(shuō), massive view controller 依然得不到解決衩匣,即中心化的 Controller 需要做大量膠水層的工作蕾总,管理各個(gè)子 Controller 。
- 用好傳統(tǒng)架構(gòu)琅捏,可以保證單個(gè)業(yè)務(wù)模塊內(nèi)的代碼的可復(fù)用性生百,但并不能避免業(yè)務(wù)之間的互相影響。簡(jiǎn)單說(shuō)柄延,就是修改業(yè)務(wù) A 的 bug 時(shí)蚀浆,可能會(huì)給業(yè)務(wù) B 引入 bug 。
- ...
歸根結(jié)底搜吧,就是因?yàn)闆](méi)有一種更為宏觀的組合模塊的架構(gòu)體系市俊。正是為了解決如何將多個(gè)業(yè)務(wù)模塊組合在一起的問(wèn)題,我設(shè)計(jì)了一套 BC 的架構(gòu)體系滤奈。
BC
BC 摆昧,全稱 BusinessController ,是一種為解決業(yè)務(wù)模塊耦合和管理問(wèn)題而生的架構(gòu)體系蜒程。
為了表明 BC 的思想和實(shí)踐效果绅你,這里我以 UIViewController 的瘦身為例進(jìn)行闡述。眾所周知昭躺, iOS 開(kāi)發(fā)最讓人頭痛的問(wèn)題之一就是 UIViewController 的代碼過(guò)于龐大忌锯,難以維護(hù)。更有網(wǎng)友戲謔稱 MVC 為 massive view controller 领炫。
Massive View Controller
iOS 系統(tǒng)默認(rèn)以 UIViewController 扮演 Controller 的角色汉规,推出一個(gè)界面就是 push 一個(gè) UIViewController 。因此作為一個(gè)界面的總管, UIViewController 管理著各個(gè)子模塊针史,也包攬了眾多的邊界模糊的工作晶伦。每當(dāng)我們需要新增一個(gè)業(yè)務(wù)功能,首先就要找到對(duì)應(yīng)的 UIViewController 啄枕,再在其中進(jìn)行編碼婚陪,如下述代碼所示:
@interface ViewController ()
@property (nonatomic, assign) BOOL A_LogicFlag;
@property (nonatomic, assign) BOOL B_LogicFlag;
... (keep adding flags)
@property (nonatomic, strong) A_ControllerClass *A_Controller;
@property (nonatomic, strong) B_ControllerClass *B_Controller;
... (keep adding modules)
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.A_Controller = [A_ControllerClass new];
[self.view addSubview:self.A_Controller.view];
__weak typeof(self) weakSelf = self;
[self.A_Controller sendRequestOnCompletion:^(BOOL success){
weakSelf.A_LogicFlag = YES;
}];
self.B_Controller = [B_ControllerClass new];
self.B_Controller.delegate = self.A_Controller;
... (keep adding code)
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
__weak typeof(self) weakSelf = self;
[self.B_Controller sendRequestOnCompletion:^(BOOL success){
weakSelf.B_LogicFlag = YES;
}];
... (keep adding code)
}
@end
以上代碼已經(jīng)把每一個(gè)業(yè)務(wù)邏輯封裝為一個(gè)個(gè)模塊,然后在 UIViewController 中管理和維系各個(gè)業(yè)務(wù)模塊間的關(guān)系频祝,這是我們?nèi)粘9ぷ髦凶畛R?jiàn)的代碼泌参。很明顯,隨著業(yè)務(wù)模塊的不斷增加常空,整個(gè) UIViewController 的代碼量將會(huì)無(wú)上限的增加沽一。并且各個(gè)業(yè)務(wù)都在這個(gè) UIViewController 中修改代碼,很容易互相引入bug漓糙,產(chǎn)生耦合铣缠。
如果有細(xì)心的讀者,會(huì)發(fā)現(xiàn)這其中還有時(shí)序問(wèn)題昆禽。怎么講蝗蛙?假設(shè)現(xiàn)在我們有一個(gè)模塊 C ,我們想要做一個(gè)小改動(dòng):將 A 模塊的初始化時(shí)機(jī)放在 C 模塊的數(shù)據(jù)請(qǐng)求返回成功后醉鳖。這是個(gè)很簡(jiǎn)單的改動(dòng)捡硅,只需將 A 模塊的初始化工作放入 C 模塊的數(shù)據(jù)請(qǐng)求返回的 completion block 里:
- (void)viewDidLoad {
[super viewDidLoad];
self.C_Controller = [C_ControllerClass new];
__weak typeof(self) weakSelf = self;
[self.C_Controller sendRequestOnCompletion:^(BOOL success){
weakSelf.A_Controller = [A_ControllerClass new];
[weakSelf.view addSubview:weakSelf.A_Controller.view];
[weakSelf.A_Controller sendRequestOnCompletion:^(BOOL success){
weakSelf.A_LogicFlag = YES;
}];
}];
self.B_Controller = [B_ControllerClass new];
self.B_Controller.delegate = self.A_Controller;
... (keep adding code)
}
若不仔細(xì)看看,難以發(fā)現(xiàn)以上代碼已經(jīng)有了 bug 盗棵。因?yàn)槲覀冄舆t了 A_Controller 的初始化壮韭,所以在 B_Controller 設(shè)置 delegate 時(shí),寫(xiě)入的 A_Controller 是 nil 纹因。這就是時(shí)序依賴喷屋, B_Controller 在設(shè)置 delegate 時(shí),要求 A_Controller 已經(jīng)完成了初始化辐怕”泼桑看似這種時(shí)序問(wèn)題在所難免从绘,其實(shí)不然寄疏。在 BC 架構(gòu)中,我將描述一種解決該時(shí)序問(wèn)題的方案僵井。
另外陕截,由于 coder 在 VC 中有著極高的自由度,所以當(dāng) coder 在做一些小特性時(shí)批什,會(huì)直接把代碼寫(xiě)在 VC 中农曲。大家為省事不再去為小功能獨(dú)立創(chuàng)建模塊,這樣 VC 中的代碼會(huì)更加混亂不堪。
- 無(wú)限增長(zhǎng)的代碼量
- 魚(yú)龍混雜的耦合關(guān)系
- 復(fù)雜的時(shí)序問(wèn)題
- 過(guò)度自由引入的混亂
- ...
讓我們來(lái)看看 BC 的架構(gòu)體系如何來(lái)解決這些問(wèn)題乳规。
BC 實(shí)現(xiàn)
我們讓 UIViewController 只負(fù)責(zé)持有和維護(hù)一個(gè)業(yè)務(wù)模塊( businessController )的數(shù)組形葬,其并不關(guān)心數(shù)組中每個(gè)業(yè)務(wù)模塊的具體實(shí)現(xiàn)。我們定義一個(gè) businessController 的基類暮的,或者協(xié)議笙以。這里我們以協(xié)議為例,定義協(xié)議 BusinessController
冻辩。
// Define.h
@protocol BusinessController <NSObject>
@end
// ViewController.h
@interface ViewController : UIViewController
@property (nonatomic, strong) NSMutableArray<id<BusinessController>> *businessControllers;
@end
首先猖腕,我們希望能夠?qū)?View Controller 的狀態(tài)事件通知給 Business Controller ,而 Business Controller 可以選擇性的實(shí)現(xiàn)這些事件恨闪。所以我們先定義一個(gè)協(xié)議 ViewControllerEvents
倘感。因?yàn)槭强蛇x擇性實(shí)現(xiàn),所以為 optional 咙咽。
// Define.h
@protocol ViewControllerEvents <NSObject>
@optional
- (void)jx_viewDidLoad;
- (void)jx_viewWillAppear;
- (void)jx_viewDidAppear;
- (void)jx_viewWillDisappear;
- (void)jx_viewDidDisappear;
// ... 其它主框架的事件也可放在這里
@end
然后使 BusinessController
遵循 ViewControllerEvents
協(xié)議老玛,這樣在 BusinessController 就有了監(jiān)聽(tīng) VC 事件的能力,并且可以自動(dòng)補(bǔ)全這些方法名犁珠。
// Define.h
@protocol BusinessController <ViewControllerEvents>
@required
// 建立一個(gè)vc的弱引用逻炊,用于訪問(wèn)vc
@property (nonatomic, weak) ViewController *viewController;
@end
接著, VC 需要向業(yè)務(wù)模塊發(fā)送這些狀態(tài)事件犁享。以 viewWillAppear
為例余素,
// ViewController.m
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.businessControllers enumerateObjectsUsingBlock:^(id<BusinessController> _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj respondsToSelector:@selector(jx_viewWillAppear)]) {
[obj jx_viewWillAppear];
}
}];
}
現(xiàn)在,當(dāng)我們需要新增一個(gè)模塊 A 時(shí)炊昆,只需使其遵循 BusinessController
協(xié)議桨吊,一切就像在一個(gè)全新的 VC 中編碼一樣,十分清爽凤巨。
// A_ControllerClass.m
- (void)jx_viewWillAppear {
// do some logic request or other business logics ...
}
最后视乐,我們只需在 VC 中添加各個(gè)業(yè)務(wù)模塊,讓整個(gè)流程跑通:
// ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
[self addBusinessControllers:@[[A_ControllerClass new],
[B_ControllerClass new],
[C_ControllerClass new],
...]];
}
至此敢茁, VC 中的代碼就被我們劃分為了許許多多的模塊佑淀。可是彰檬,業(yè)務(wù)模塊之間伸刃,是需要通信的,那我們又如何解決這個(gè)通信問(wèn)題呢逢倍?我們最容易想到的是兩種常規(guī)的通信方式—— NSNotification 和 delegate 捧颅。
首先, NSNotification 是不合適的较雕。這是一種全局通知碉哑,整個(gè) APP 都會(huì)收到。我們希望的結(jié)果是, ViewController
實(shí)例一中的模塊 A 給模塊 B 發(fā)消息時(shí)扣典,不會(huì)發(fā)送到 ViewController
實(shí)例二中的模塊 B 去妆毕。
那我們就用 delegate 吧?—— NO贮尖! 第一设塔,使用 delegate 我們需要不斷的去維護(hù)那些對(duì)象之間的 delegate 關(guān)系(即在 VC 中編寫(xiě) delegate 的依賴關(guān)系,A.delegate = B
)远舅,這也會(huì)引入 Massive View Controller 中提到的時(shí)序問(wèn)題闰蛔。第二,若是模塊 A 的代理事件模塊 B 和模塊 C 都需要監(jiān)聽(tīng)图柏,我們還需要將 delegate 做成數(shù)組序六。咦,真夠惡心蚤吹。
所以例诀,我們能否找到一種更好的方式來(lái)解決通信問(wèn)題呢?
這里我提供的解決方案是使用 OC 的消息轉(zhuǎn)發(fā)特性(對(duì)消息轉(zhuǎn)發(fā)不太了解的同學(xué)裁着,可以學(xué)習(xí)一下《Effective Objective-C 2.0》中消息轉(zhuǎn)發(fā)的章節(jié))繁涂。首先我們創(chuàng)建一個(gè)消息中心 CommunicationCenter
,一個(gè)消息協(xié)議 BusinessControllerConversation
二驰。讓消息中心遵循消息協(xié)議扔罪,但其內(nèi)部不實(shí)現(xiàn)任何方法,其只做轉(zhuǎn)發(fā)桶雀,將消息轉(zhuǎn)發(fā)給每一個(gè)實(shí)現(xiàn)了該消息的業(yè)務(wù)模塊( BC )矿酵。接收消息的 BC 也遵循 BusinessControllerConversation
協(xié)議。
// Define.h
@protocol BusinessControllerConversation <NSObject>
@end
// Define.h
@protocol BusinessController <ViewControllerEvents, BusinessControllerConversation>
@required
// 建立一個(gè)vc的弱引用矗积,用于訪問(wèn)vc
@property (nonatomic, weak) ViewController *viewController;
@end
// CommunicationCenter.m
@interface CommunicationCenter : NSObject <BusinessControllerConversation>
// 建立一個(gè)vc的弱引用全肮,用于訪問(wèn)vc
@property (nonatomic, weak) ViewController *viewController;
@end
// CommunicationCenter.m
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL selector = anInvocation.selector;
[self.viewController.businessControllers enumerateObjectsUsingBlock:^(id<BusinessController> _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj respondsToSelector:selector]) {
[anInvocation invokeWithTarget:obj];
}
}];
}
接著,我們?cè)?VC 中創(chuàng)建并持有一個(gè)消息中心棘捣。
// ViewController.h
@property (nonatomic, strong) CommunicationCenter *communicationCenter;
// ViewController.m
_communicationCenter = [CommunicationCenter new];
這樣辜腺,當(dāng)我們的業(yè)務(wù)模塊之間需要通信時(shí),將消息定義在 BusinessControllerConversation
中乍恐,然后直接向消息中心發(fā)送消息即可评疗。例如當(dāng)前頁(yè)面的刷新按鈕被點(diǎn)擊了,但管理刷新按鈕的模塊并不管當(dāng)前頁(yè)面有哪些模塊需要刷新禁熏,它只管將該消息拋到消息中心壤巷。而需要刷新的業(yè)務(wù)模塊邑彪,則實(shí)現(xiàn)該消息即可瞧毙。
// Define.h
@protocol BusinessControllerConversation <NSObject>
@optional
- (void)msg_refreshButtonClicked;
@end
// B_ControllerClass.m
- (void)refreshBtnClicked {
[self.viewController.communicationCenter msg_refreshBtnClicked];
}
// A_ControllerClass.m
- (void)msg_refreshBtnClicked {
// do some business logic ...
}
由此,我們實(shí)現(xiàn)了單個(gè)VC中,模塊之間一對(duì)多的互相通信宙彪。這里值得注意的是矩动,模塊 A 和模塊 B 的耦合度幾乎降至最低。因?yàn)?A 和 B 之間互相都不知道對(duì)方释漆,不需要設(shè)置對(duì)方為 delegate 悲没,也不會(huì)有建立依賴的時(shí)序問(wèn)題。 BC 都全部面向消息編程男图,即面向協(xié)議編程示姿。
這就是使用 CommunicationCenter 進(jìn)行統(tǒng)一轉(zhuǎn)發(fā)的通信方式所帶來(lái)的極大好處:消息發(fā)送方不需要關(guān)心誰(shuí)接收消息,其只管通知一下某事件發(fā)生了逊笆。消息接收方也不需要關(guān)心誰(shuí)發(fā)送的消息栈戳,其只管接收消息做出反應(yīng)。這樣使業(yè)務(wù)模塊間的耦合性降至最低难裆。
不難發(fā)現(xiàn)子檀,只要是業(yè)務(wù)模塊 BC 所需要的事件,我們都可以通過(guò) CommunicationCenter
進(jìn)行轉(zhuǎn)發(fā)乃戈。所以我們讓 CommunicationCenter
和 BusinessController
遵循 ViewControllerEvents
協(xié)議褂痰,這樣 ViewController
中的狀態(tài)事件,我們直接拋給 CommunicationCenter
即可症虑。狀態(tài)事件會(huì)經(jīng)過(guò) CommunicationCenter
路由至業(yè)務(wù) BC 缩歪。
// ViewController.m
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.communicationCenter jx_viewWillAppear];
}
在采用 BC 的架構(gòu)之后,所有的模塊都需要?jiǎng)?chuàng)建 BC 谍憔,再也沒(méi)有隨意散落在 VC 中的代碼驶冒。
至此,我們實(shí)現(xiàn)了將 VC 中的業(yè)務(wù)模塊逐一打散韵卤,各自為營(yíng)骗污,也支持業(yè)務(wù)模塊之間的靈活通信。其代碼量無(wú)限增長(zhǎng)的問(wèn)題沈条、代碼糅雜在一起魚(yú)龍混雜的問(wèn)題等需忿,都得到了解決。
BC 與傳統(tǒng)架構(gòu)
BC 設(shè)計(jì)模式的通信結(jié)構(gòu)如上圖所示( Owner 即文中的 ViewController )蜡歹。 Owner 將主流程事件發(fā)至消息中心屋厘,由消息中心路由至各個(gè) Module 。而各個(gè) Module 之間也通過(guò)消息中心轉(zhuǎn)發(fā)至其他 Module 月而。
可以看到 BC 和傳統(tǒng)的 MVC 汗洒, MVVM , VIPER 的關(guān)系不是互斥的,是并存的父款。從 MVC 到 MVVM 到 VIPER 是對(duì)架構(gòu)的不斷細(xì)化溢谤。而 BC 則是提供了一種劃分模塊的機(jī)制瞻凤。即一個(gè) Module 可以是 Model ,可以是 View 世杀,也可以是包含了 MVC 的一個(gè)完整的模塊阀参。在使用 MVC , MVVM 瞻坝, VIPER 等設(shè)計(jì)模式時(shí)蛛壳,我們可以同時(shí)使用 BC 來(lái)幫助我們組織各個(gè)模塊。通過(guò) BC 所刀,我們將根據(jù)不同架構(gòu)設(shè)計(jì)的不同模塊有機(jī)的結(jié)合了起來(lái)衙荐。