模塊化的意義
當項目大到一定程度,開發人員也多,所有的代碼都集中到一個倉庫,提交修改都要等其他人提交完成不報錯才能開始,非常麻煩。
再者就是代碼之間耦合嚴重,到處引用,穿插錯綜複雜,往往改變一個變量,需要修改很多處代碼,很容易出錯。
對於這種情況,就要進行架構整治,模塊化無疑是一個好的方案。
模塊化的粒度
對於模塊化,並不是一味的全部分離成模塊就是最好的。模塊之間或許會有必要的引用以及上下級依賴關係,沒必要完全的獨立。再者需要考慮到業務變化時,可能又要重新劃分模塊,工作量和成本又高了。
iOS 模塊化的劃分應該遵循 SOLID原則 ,如下幾點:
(S)單一原則
:對象的功能要單一,不要是很多功能的集合體
例如CALayer
負責動畫視圖顯示,UIView
則負責事件傳遞,事件響應
(O)開閉原則
:“開”,是指對於組件功能的擴展是開放的,是允許對其進行功能擴展的;開閉原則中“閉”,是指對於原有代碼的修改是封閉的,即不應該修改原有的代碼。(L)里氏替換原則
:子類對象是可以替換基類對象的。所有引用基類的地方必須能透明地使用其子類的對象。
例如KVO
的實現機制,利用isa-swizzling
父類指向子類。
(I)接口隔離原則
:接口的用途要單一,不要一個接口上根據入參不同實現不同的功能。
可以使用多個專門的協議,而不是使用一個龐大臃腫的協議。
(D)依賴反轉原則
:方法應該依賴抽象,不要依賴實例。面向接口編程,不要面向實現編程。讓調用接口感覺不到內部是如何操作。
還一個 (D)迪米特法則
:如果兩個軟件實體無須直接通信,那麼就不應當發生直接的相互調用,可以通過第三方轉發該調用。其目的是降低類之間的耦合度,提高模塊的相對獨立性。即高聚合,低耦合。
可以順帶記憶
組件
可組裝,獨立的業務單元,高內聚,低耦合的特性。
iOS開發的組件,不應是UI控件
,也不是UIViewController
這種大UI和功能的集合,應該是包含UI控件
,小功能集合的組件這樣劃分。
組件化方案
MGJRouter 的路由映射
https://github.com/meili/MGJRouter
蘑菇街的組件化方案 MGJRouter
[MGJRouter registerURLPattern:@"mgj://detail?id=:id" toHandler:^(NSDictionary *routerParameters) {
NSNumber *id = routerParameters[@"id"];
// create view controller with id
// push view controller
}];
是一個路由方案url-block
,通過註冊url
和block
到一個單例中的字典routes
中。
當調用
[MGJRouter openURL:@"mgj://detail?id=404"]
就通過Url
去從字典routes
取其block
來執行。這樣內存中維護了很多映射關係,Url
這樣的使用方式,也比較繁瑣。而且需要將Url
的時機必須要在調用之前,在+load
進行註冊又拖長了App啓動時間,在其他時機註冊又需要統籌兼顧。
另外還有protocol-class
和url-controller
,這些方式都大同小異,需要維護一個映射表,是一個短板,個人更傾向於CTMediator
這種中間者架構
CTMediator 中間者
https://github.com/casatwy/CTMediator
其實就是使用runtime
通過[target performSelector:action withObject:params];
取到需要的數據,例如界面,視圖。
如何使用這些界面視圖就不關CTMediator
的事了,這些界面,視圖就可以封裝成單獨的模塊,互無關係。
Category的編寫
CTMediator
的關鍵在於其分類的編寫,每個模塊功能的分類由其模塊開發者編寫。
- (UIViewController *)CTMediator_viewControllerForDetail
{
UIViewController *viewController = [self performTarget:kCTMediatorTargetA
action:kCTMediatorActionNativeFetchDetailViewController
params:@{@"key":@"value"}
shouldCacheTarget:NO
];
if ([viewController isKindOfClass:[UIViewController class]]) {
// view controller 交付出去之後,可以由外界選擇是push還是present
return viewController;
} else {
// 這裏處理異常場景,具體如何處理取決於產品
return [[UIViewController alloc] init];
}
}
這是demo
中分類的一個方法實現,kCTMediatorTargetA
隸屬於模塊A
kCTMediatorTargetA
中有一個
kCTMediatorActionNativeFetchDetailViewController方法可以獲取一個
UIViewController`,
通過寫CTMediator
對於模塊A的分類,就可以獲取模塊A中相關的界面以及UI,例如CTMediator_viewControllerForDetail
。
這樣的優勢在於,沒有了key-value
的映射關係,不需要維護url映射表
,更不需要存儲block塊,Category For A
由開發模塊A
的人員來寫,他很熟悉模塊A
(對於他來說將模塊A
中某個界面取出,並設置相關參數是很簡單的事情),通過分類再轉給不熟悉的人(無需關心模塊A
的內容)使用,就很方便了。
對於遠程url
調用,同樣是通過將url
解析成對應的動作,例如取某個視圖,推出某個界面。
組件化能讓工程架構更加清晰,每個人負責一個模塊,通過pod導入,互不影響。