iOS開發(fā)-淺談組件化方案

最近在學(xué)習(xí)組件化的一些方案逗爹。這里收集消化了一下亡嫌,分享給大家參考。

組件化是架構(gòu)層面的一個(gè)概念掘而,它把項(xiàng)目按照某些規(guī)則(比如:按功能挟冠、按業(yè)務(wù))劃分成若干個(gè)顆粒度較小的單位,我們把這些單位稱之為組件袍睡,或者是模塊知染,來達(dá)到優(yōu)化項(xiàng)目結(jié)構(gòu)的目的。

組件又可以細(xì)分為 功能組件(如:圖片庫斑胜,網(wǎng)絡(luò)庫)控淡,業(yè)務(wù)組件也叫模塊(如:訂單模塊,個(gè)人中心模塊)

功能組件主要是物理層面的拆分止潘,方便以后的復(fù)用

業(yè)務(wù)組件強(qiáng)調(diào)邏輯拆分掺炭,以便解耦

組件化的發(fā)展歷程

開發(fā)之初,在功能方面凭戴,我們會(huì)把項(xiàng)目劃分為基礎(chǔ)層涧狮、網(wǎng)絡(luò)層數(shù)據(jù)層等等么夫,而業(yè)務(wù)層面者冤,僅僅是按照目錄結(jié)構(gòu)做一個(gè)簡單的模塊分層,比如訂單模塊档痪、個(gè)人中心模塊

注:因?yàn)楣δ芙M件被大部分業(yè)務(wù)模塊所依賴涉枫,這里暫時(shí)不討論功能模塊的架構(gòu)問題。

隨著業(yè)務(wù)的發(fā)展腐螟,項(xiàng)目變的越來越復(fù)雜愿汰,APP內(nèi)各個(gè)業(yè)務(wù)之間的耦合嚴(yán)重困后,邊界越來越模糊,經(jīng)常會(huì)出現(xiàn)你中有我尼桶,我中有你的情況操灿,如圖

傳統(tǒng)耦合圖.jpg

可以看到锯仪,模塊與模塊嚴(yán)重耦合泵督,對(duì)代碼的擴(kuò)展,以及代碼的開發(fā)效率造成了巨大影響庶喜,有一種改一處動(dòng)全身的感覺小腊。發(fā)展到這個(gè)階段,我們會(huì)把各個(gè)模塊分割開來久窟,通過中介者來完成不同模塊之間的交互秩冈。如圖

中介者.jpg

這個(gè)時(shí)候,架構(gòu)看起來是清晰了許多斥扛。但因?yàn)橹薪檎呷稳环聪蛞蕾嚇I(yè)務(wù)模塊入问,任然存在改一處多個(gè)模塊受影響的情況,依賴仍舊是雙向的稀颁。我們舉個(gè)例子:

假如我現(xiàn)在在會(huì)員模塊芬失,要跳入到商品模塊除秀,此時(shí)活翩,會(huì)員模塊需要通過中介者來完成跳轉(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ù)來決定導(dǎo)航條的隱藏或者背景色阎肝,此時(shí)我們修改gotoGoodsModuleWithParam函數(shù)的結(jié)構(gòu)颖系,那么就需要修改兩個(gè)地方凑阶,除了修改中介者中的方法以外稻据,還需要修改模塊中使用該函數(shù)的地方秃踩。

互相依賴其實(shí)是開發(fā)時(shí)候的大忌衬鱼,我們還要考慮模塊的循環(huán)引用問題、開發(fā)效率的問題憔杨、復(fù)用問題等等

如果消除中介者對(duì)模塊的依賴呢鸟赫?如圖:

中間件.jpg

是不是有那味道了。各模塊互不干涉芍秆,明確職責(zé)和邊界惯疙。

那么如何實(shí)現(xiàn)這種功能呢?下面來看看業(yè)內(nèi)的一些技術(shù)方案妖啥。

業(yè)內(nèi)組件化方案

  1. 基于路由URL的UI頁面統(tǒng)跳管理
  2. 基于反射的接口調(diào)用封裝
  3. 基于面向協(xié)議思想的服務(wù)注冊(cè)方案
  4. 基于通知的廣播方案

基于路由URL的UI頁面統(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ù)類型

+ (nullable id)handleURL:(nonnull NSString *)urlStr
           complexParams:(nullable NSDictionary*)complexParams
              completion:(nullable RouteCompletion)completion;

從接口上可以看出來霉颠,通過@"/goods/goods_detail"字符串,我們可以拿到需要交互的模塊荆虱,來進(jìn)行操作蒿偎。大家可以參考Bifrost朽们,有一定知識(shí)儲(chǔ)備的話,看起來會(huì)比較容易诉位。當(dāng)然這只是其中一種思路骑脱。

Demo中,作者把各個(gè)模塊以子項(xiàng)目的形式匯聚在一個(gè)主工程里苍糠,在需要路由服務(wù)的頁面叁丧,通過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:");
...

然后可以通過- (id)performSelector:(SEL)aSelector來完成消息的發(fā)送柱宦。

但是這種方式存在大量的硬編碼些椒,也無法觸發(fā)編譯器的自動(dòng)補(bǔ)全,同時(shí)捷沸,只有在運(yùn)行時(shí)才可以發(fā)現(xiàn)一些未知的錯(cuò)誤摊沉。除此之外,無法實(shí)現(xiàn)多參數(shù)傳值和方法返回值的獲取的問題痒给。所以這里選擇NSInvocation更合適

這里可以看下業(yè)界的 CTMediator 開源庫说墨。是基于Mediator模式和Target-Action模式來完成的

先說調(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信息,通過objective-Cruntime轉(zhuǎn)化生成target實(shí)例以及對(duì)應(yīng)的action選擇項(xiàng)试吁,然后最終調(diào)用到目標(biāo)業(yè)務(wù)提供的邏輯棺棵,完成需求。

組件僅需要通過Action暴露可調(diào)用的接口即可熄捍。所有組件都通過組件自帶的Target-Action來響應(yīng)烛恤,也就是說,模塊與模塊之間的接口被固化在了Target-Action這一層余耽,避免了實(shí)施組件化的改造過程中缚柏,對(duì)Business的侵入,同時(shí)也提高了組件化接口的可維護(hù)性碟贾。

最后币喧,調(diào)用者通過響應(yīng)者給 CTMediator 做的 category 或者 extension 來發(fā)起調(diào)用轨域,來避免使用字符串調(diào)用出現(xiàn)的不友好。

代碼大家可以看下杀餐,可以說非常簡潔了干发。考慮的也非常全面史翘。關(guān)于一些架構(gòu)的思想可以看下大佬casatwy的文章iOS應(yīng)用架構(gòu)談 組件化方案枉长。

簡化下代碼如下:

// 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é)議編程脉让,看起來是很酷的。而且也不需要寫反射代碼功炮。

但是:把協(xié)議的內(nèi)容放到公共的地方溅潜,一旦發(fā)生改變,意味著用到協(xié)議的地方都要改一遍薪伏,而且滚澜,load方法中進(jìn)行協(xié)議綁定,還是會(huì)有老毛病存在嫁怀。

有贊團(tuán)隊(duì)设捐,對(duì)基于服務(wù)注冊(cè)所消耗的啟動(dòng)時(shí)間做了測(cè)試,答案是影響可以忽略不計(jì)塘淑。

通知廣播方案

基于通知的模塊間通訊的方案萝招,實(shí)現(xiàn)起來是最簡單的,直接基于系統(tǒng)提供的NSNotificationCenter即可存捺。適合一對(duì)多的通訊場(chǎng)景槐沼。但是劣勢(shì)也特別明顯。復(fù)雜的數(shù)據(jù)傳輸捌治,同步調(diào)用等方式都不太方便岗钩。通常用來作為以上幾種方案的補(bǔ)充。

總結(jié)

組件化是項(xiàng)目發(fā)展到一定程度后的一種選擇肖油。如果不考慮時(shí)間成本和技術(shù)成本的話兼吓,是可以去搞一下的。雖然一開始的技術(shù)壁壘較高构韵,但是對(duì)后續(xù)的迭代和升級(jí)是非常有幫助的周蹭。

多提一句趋艘。我們?cè)谧黾軜?gòu)的時(shí)候,一定是基于自己的實(shí)際項(xiàng)目來搞的凶朗,業(yè)界很多優(yōu)秀的思想可以拿來參考瓷胧,沒有必要生搬硬套。因?yàn)椴]有絕對(duì)正確的架構(gòu)棚愤,只有最適合自己的架構(gòu)搓萧。

以上就是對(duì)組件化的一些整理和思考,希望對(duì)大家有幫助宛畦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瘸洛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子次和,更是在濱河造成了極大的恐慌反肋,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件踏施,死亡現(xiàn)場(chǎng)離奇詭異石蔗,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)畅形,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門养距,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人日熬,你說我怎么就攤上這事棍厌。” “怎么了竖席?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵耘纱,是天一觀的道長。 經(jīng)常有香客問我怕敬,道長揣炕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任东跪,我火速辦了婚禮畸陡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘虽填。我一直安慰自己丁恭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布斋日。 她就那樣靜靜地躺著牲览,像睡著了一般。 火紅的嫁衣襯著肌膚如雪恶守。 梳的紋絲不亂的頭發(fā)上第献,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天贡必,我揣著相機(jī)與錄音,去河邊找鬼庸毫。 笑死仔拟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的飒赃。 我是一名探鬼主播利花,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼载佳!你這毒婦竟也來了炒事?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤蔫慧,失蹤者是張志新(化名)和其女友劉穎挠乳,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體藕漱,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡欲侮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了肋联。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡刁俭,死狀恐怖橄仍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情牍戚,我是刑警寧澤侮繁,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站如孝,受9級(jí)特大地震影響宪哩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜第晰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一锁孟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧茁瘦,春花似錦品抽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至腔稀,卻和暖如春盆昙,著一層夾襖步出監(jiān)牢的瞬間羽历,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工淡喜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秕磷,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓拆火,卻偏偏與公主長得像跳夭,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子们镜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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