更多iOS資料
背景
組件化是架構(gòu)層面的一個(gè)概念城榛,它把項(xiàng)目按照某些規(guī)則(比如:按功能揪利、按業(yè)務(wù))劃分成若干個(gè)顆粒度較小的單位,我們把這些單位稱之為組件狠持,或者是模塊疟位,來(lái)達(dá)到優(yōu)化項(xiàng)目結(jié)構(gòu)的目的。
組件又可以細(xì)分為 功能組件(如:圖片庫(kù)喘垂,網(wǎng)絡(luò)庫(kù))甜刻,業(yè)務(wù)組件也叫模塊(如:訂單模塊,個(gè)人中心模塊)
功能組件主要是物理層面的拆分正勒,方便以后的復(fù)用
業(yè)務(wù)組件強(qiáng)調(diào)邏輯拆分得院,以便解耦
組件化的發(fā)展歷程
開(kāi)發(fā)之初,在功能方面章贞,我們會(huì)把項(xiàng)目劃分為基礎(chǔ)層祥绞、網(wǎng)絡(luò)層、數(shù)據(jù)層等等阱驾,而業(yè)務(wù)層面就谜,僅僅是按照目錄結(jié)構(gòu)做一個(gè)簡(jiǎn)單的模塊分層,比如訂單模塊里覆、個(gè)人中心模塊
注:因?yàn)楣δ芙M件被大部分業(yè)務(wù)模塊所依賴丧荐,這里暫時(shí)不討論功能模塊的架構(gòu)問(wèn)題。
隨著業(yè)務(wù)的發(fā)展喧枷,項(xiàng)目變的越來(lái)越復(fù)雜虹统,APP內(nèi)各個(gè)業(yè)務(wù)之間的耦合嚴(yán)重,邊界越來(lái)越模糊隧甚,經(jīng)常會(huì)出現(xiàn)你中有我车荔,我中有你的情況,如圖:
可以看到戚扳,模塊與模塊嚴(yán)重耦合忧便,對(duì)代碼的擴(kuò)展,以及代碼的開(kāi)發(fā)效率造成了巨大影響帽借,有一種改一處動(dòng)全身的感覺(jué)珠增。發(fā)展到這個(gè)階段超歌,我們會(huì)把各個(gè)模塊分割開(kāi)來(lái),通過(guò)中介者來(lái)完成不同模塊之間的交互蒂教。如圖:
這個(gè)時(shí)候巍举,架構(gòu)看起來(lái)是清晰了許多。但因?yàn)橹薪檎呷稳环聪蛞蕾嚇I(yè)務(wù)模塊凝垛,任然存在改一處多個(gè)模塊受影響的情況懊悯,依賴仍舊是雙向的。我們舉個(gè)例子:
假如我現(xiàn)在在會(huì)員模塊梦皮,要跳入到商品模塊炭分,此時(shí),會(huì)員模塊需要通過(guò)中介者來(lái)完成跳轉(zhuǎn)
// 會(huì)員模塊
[self.Intermediary gotoGoodsModuleWithParam:param]
// 在中介者中
- (void)gotoGoodsModuleWithParam:(id)param {
self.goodsModule.param = param; // 參數(shù)
[self.memberModule.navigationController pushViewController:self.goodsModule]
}
假如此時(shí)我們的需求變了届氢,除了傳遞參數(shù)外欠窒,還需要額外傳一個(gè)參數(shù)來(lái)決定導(dǎo)航條的隱藏或者背景色,此時(shí)我們修改gotoGoodsModuleWithParam函數(shù)的結(jié)構(gòu)退子,那么就需要修改兩個(gè)地方,除了修改中介者中的方法以外型将,還需要修改模塊中使用該函數(shù)的地方寂祥。
互相依賴其實(shí)是開(kāi)發(fā)時(shí)候的大忌,我們還要考慮模塊的循環(huán)引用問(wèn)題七兜、開(kāi)發(fā)效率的問(wèn)題丸凭、復(fù)用問(wèn)題等等
如果消除中介者對(duì)模塊的依賴呢?如圖:
是不是有那味道了腕铸。各模塊互不干涉惜犀,明確職責(zé)和邊界。
那么如何實(shí)現(xiàn)這種功能呢狠裹?下面來(lái)看看業(yè)內(nèi)的一些技術(shù)方案虽界。
內(nèi)業(yè)組件化方案
1.基于路由URL的UI頁(yè)面統(tǒng)跳管理
2.基于反射的接口調(diào)用封裝
3.基于面向協(xié)議思想的服務(wù)注冊(cè)方案
4.基于通知的廣播方案
基于路由URL的UI頁(yè)面統(tǒng)跳管理
一般用法
// kRouteGoodsDetail = @"/goods/goods_detail"
UIViewController * vc = [Router handleURL:kRouteGoodsDetail];
if (vc) [self.navigationController pushViewController:vc animated:YES];
傳參的情況
//kRouteGoodsDetails = @“//goods/goods_detail?goods_id=%d”
NSString *urlStr = [NSString stringWithFormat:kRouteGoodsDetails, 123];
UIViewController *vc = [Router handleURL:urlStr];
if(vc) {
[self.navigationController pushViewController:vc animated:YES];
}
復(fù)雜的參數(shù)類(lèi)型
+ (nullable id)handleURL:(nonnull NSString *)urlStr
complexParams:(nullable NSDictionary*)complexParams
completion:(nullable RouteCompletion)completion;
從接口上可以看出來(lái),通過(guò)@"/goods/goods_detail"
字符串涛菠,我們可以拿到需要交互的模塊莉御,來(lái)進(jìn)行操作。大家可以參考Bifrost俗冻,有一定知識(shí)儲(chǔ)備的話礁叔,看起來(lái)會(huì)比較容易。當(dāng)然這只是其中一種思路迄薄。
在Demo
中琅关,作者把各個(gè)模塊以子項(xiàng)目的形式匯聚在一個(gè)主工程里,在需要路由服務(wù)的頁(yè)面讥蔽,通過(guò)bindURL
的形式涣易,完成字符串
與路由
的綁定画机。
但是:路由的綁定放到load
方法中,會(huì)對(duì)項(xiàng)目的冷啟動(dòng)有一定的影響都毒,而且色罚,把所有的業(yè)務(wù)功能組件綁定勢(shì)必造成不必要的內(nèi)存常駐。
當(dāng)然账劲,有贊團(tuán)隊(duì)進(jìn)行了很多的測(cè)試和思考戳护,最終方案的敲定肯定是值得信賴的。
基于反射的接口調(diào)用封裝
大家知道OC是支持反射的瀑焦,比如:
Class className = NSClassFromString(@"Person");
SEL sel = NSSelectorFromString(@"getPersonName:");
...
然后可以通過(guò)- (id)performSelector:(SEL)aSelector
來(lái)完成消息的發(fā)送腌且。
但是這種方式存在大量的硬編碼,也無(wú)法觸發(fā)編譯器的自動(dòng)補(bǔ)全榛瓮,同時(shí)铺董,只有在運(yùn)行時(shí)才可以發(fā)現(xiàn)一些未知的錯(cuò)誤。除此之外禀晓,無(wú)法實(shí)現(xiàn)多參數(shù)傳值和方法返回值的獲取的問(wèn)題精续。所以這里選擇NSInvocation
更合適
這里可以看下業(yè)界的 CTMediator 開(kāi)源庫(kù)。是基于Mediator
模式和Target-Action
模式來(lái)完成的
先說(shuō)調(diào)用方法:(状饫痢:本次只討論本地模塊之間的調(diào)用重付,不考慮遠(yuǎn)程調(diào)用)
本地組件A在某處調(diào)用[[CTMediator sharedInstance] performTarget:targetName action:actionName params:@{...}]
向CTMediator
發(fā)起跨組件調(diào)用,CTMediator
根據(jù)獲得的target和action信息
凫乖,通過(guò)objective-C
的runtime
轉(zhuǎn)化生成target
實(shí)例以及對(duì)應(yīng)的action
選擇項(xiàng)确垫,然后最終調(diào)用到目標(biāo)業(yè)務(wù)提供的邏輯帽芽,完成需求导街。
組件僅需要通過(guò)Action
暴露可調(diào)用的接口即可。所有組件都通過(guò)組件自帶的Target-Action
來(lái)響應(yīng),也就是說(shuō),模塊與模塊之間的接口被固化在了Target-Action
這一層陶冷,避免了實(shí)施組件化的改造過(guò)程中,對(duì)Business
的侵入胀莹,同時(shí)也提高了組件化接口的可維護(hù)性。
最后篱竭,調(diào)用者通過(guò)響應(yīng)者給 CTMediator
做的 category
或者 extension
來(lái)發(fā)起調(diào)用,來(lái)避免使用字符串調(diào)用出現(xiàn)的不友好。
代碼大家可以看下,可以說(shuō)非常簡(jiǎn)潔了√钐В考慮的也非常全面仆潮。關(guān)于一些架構(gòu)的思想可以看下大佬casatwy
的文章iOS應(yīng)用架構(gòu)談 組件化方案。
簡(jiǎn)化下代碼如下:
// Mediator提供基于NSInvocation的接口調(diào)用方法的統(tǒng)一入口
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params;
// 業(yè)務(wù)模塊對(duì)外提供的方法封裝到Category中
@interface CTMediator (Goods)
- (NSArray *)goods_getGoodsList;
- (void)goods_gotoGoodsDetail:(NSString *)id;
...
@end
@impletation CTMediator(Goods)
- (NSArray *)goods_getGoodsList{
return [self performTarget:@“Target_Goods” action:@"getGoodsList" params:nil];
}
- (void *)goods_gotoGoodsDetail:(NSString *)id{
return [self performTarget:@“Target_Goods” action:@"gotoGoodsDetail" params:{@"id":id}];
}
@interface Target_Goods : NSObject
- (NSArray *)getGoodsList;
- (void)gotoGoodsDetail:(NSString *)id;
@end
基于面向協(xié)議思想的服務(wù)注冊(cè)方案
每個(gè)模塊提供自己的服務(wù)協(xié)議遣臼,然后將此協(xié)議聲明注冊(cè)到中間層性置。調(diào)用方能從中間層看到有哪些服務(wù)的接口,然后直接調(diào)用即可揍堰。
// 在中間件中完成協(xié)議的聲明鹏浅,方便所有模塊調(diào)用和查閱
@protocol GoodsModuleService
- (NSArray*)getGoodsList;
- (NSInteger)getGoodsCount;
...
@end
// 在模塊的load方法中,注冊(cè)協(xié)議个榕。并且讓該模塊實(shí)現(xiàn)協(xié)議中的方法
@interface GoodsModule : NSObject<GoodsModuleService>
@end
@implementation GoodsModule
+ (void)load {
[ServiceManager registerService:@protocol(GoodsModuleService) withModule:self.class]
}
//提供具體實(shí)現(xiàn)
- (NSArray*)getGoodsList {...}
- (NSInteger)getGoodsCount {...}
// 在其他模塊中調(diào)用
id<GoodsModuleService> goodsModule = [ServiceManager objByService:@protocol(GoodsModuleService)];
NSArray *list = [goodsModule getGoodsList];
...
面向協(xié)議編程篡石,看起來(lái)是很酷的。而且也不需要寫(xiě)反射代碼西采。
但是:把協(xié)議的內(nèi)容放到公共的地方凰萨,一旦發(fā)生改變,意味著用到協(xié)議的地方都要改一遍械馆,而且胖眷,load方法中進(jìn)行協(xié)議綁定,還是會(huì)有老毛病存在霹崎。
有贊團(tuán)隊(duì)珊搀,對(duì)基于服務(wù)注冊(cè)所消耗的啟動(dòng)時(shí)間做了測(cè)試,答案是影響可以忽略不計(jì)尾菇。
通知方案廣播
基于通知的模塊間通訊的方案境析,實(shí)現(xiàn)起來(lái)是最簡(jiǎn)單的,直接基于系統(tǒng)提供的NSNotificationCenter即可派诬。適合一對(duì)多的通訊場(chǎng)景劳淆。但是劣勢(shì)也特別明顯。復(fù)雜的數(shù)據(jù)傳輸默赂,同步調(diào)用等方式都不太方便沛鸵。通常用來(lái)作為以上幾種方案的補(bǔ)充。
總結(jié)
組件化是項(xiàng)目發(fā)展到一定程度后的一種選擇缆八。如果不考慮時(shí)間成本和技術(shù)成本的話曲掰,是可以去搞一下的。雖然一開(kāi)始的技術(shù)壁壘較高奈辰,但是對(duì)后續(xù)的迭代和升級(jí)是非常有幫助的栏妖。
多提一句。我們?cè)谧黾軜?gòu)的時(shí)候冯挎,一定是基于自己的實(shí)際項(xiàng)目來(lái)搞的底哥,業(yè)界很多優(yōu)秀的思想可以拿來(lái)參考咙鞍,沒(méi)有必要生搬硬套。因?yàn)椴](méi)有絕對(duì)正確的架構(gòu)趾徽,只有最適合自己的架構(gòu)续滋。
以上就是對(duì)組件化的一些整理和思考,希望對(duì)大家有幫助孵奶。