組件化搞不懂傲武?這篇文章為你詳解。iOS開(kāi)發(fā)之架構(gòu)組件化

更多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)你中有我车荔,我中有你的情況,如圖:

image

可以看到戚扳,模塊與模塊嚴(yán)重耦合忧便,對(duì)代碼的擴(kuò)展,以及代碼的開(kāi)發(fā)效率造成了巨大影響帽借,有一種改一處動(dòng)全身的感覺(jué)珠增。發(fā)展到這個(gè)階段超歌,我們會(huì)把各個(gè)模塊分割開(kāi)來(lái),通過(guò)中介者來(lái)完成不同模塊之間的交互蒂教。如圖:
image

這個(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ì)模塊的依賴呢?如圖:

image

是不是有那味道了腕铸。各模塊互不干涉惜犀,明確職責(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-Cruntime轉(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ì)大家有幫助孵奶。


更多學(xué)習(xí)資料


原文轉(zhuǎn)載

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末疲酌,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子了袁,更是在濱河造成了極大的恐慌朗恳,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件载绿,死亡現(xiàn)場(chǎng)離奇詭異粥诫,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)崭庸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén)怀浆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人怕享,你說(shuō)我怎么就攤上這事执赡。” “怎么了函筋?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵沙合,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我跌帐,道長(zhǎng)首懈,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任谨敛,我火速辦了婚禮猜拾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘佣盒。我一直安慰自己,他們只是感情好顽聂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布肥惭。 她就那樣靜靜地躺著,像睡著了一般紊搪。 火紅的嫁衣襯著肌膚如雪蜜葱。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,775評(píng)論 1 307
  • 那天耀石,我揣著相機(jī)與錄音牵囤,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛揭鳞,可吹牛的內(nèi)容都是我干的炕贵。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼野崇,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼称开!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起乓梨,我...
    開(kāi)封第一講書(shū)人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鳖轰,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后扶镀,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蕴侣,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年臭觉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了昆雀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡胧谈,死狀恐怖忆肾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情菱肖,我是刑警寧澤客冈,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站稳强,受9級(jí)特大地震影響场仲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜退疫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一渠缕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧褒繁,春花似錦亦鳞、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至坝冕,卻和暖如春徒探,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背喂窟。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工测暗, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留央串,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓碗啄,卻偏偏與公主長(zhǎng)得像质和,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挫掏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

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