一、為什么組件化
1、模塊間解耦
每個(gè)模塊盡可能可以進(jìn)行獨(dú)立開(kāi)發(fā)、調(diào)試、測(cè)試、打包等,模塊內(nèi)部改動(dòng)不影響或盡量少影響主APP和其他模塊;
2、模塊重用
模塊解耦實(shí)際上也是為了更好的復(fù)用。一個(gè)模塊可能會(huì)被多個(gè)模塊或者其他app所使用的,那么組件化把模塊獨(dú)立出來(lái),方便復(fù)用。
3、提高團(tuán)隊(duì)協(xié)作開(kāi)發(fā)效率
組件化使得團(tuán)隊(duì)成員分工更加明確,各自負(fù)責(zé)各自的業(yè)務(wù)線。每個(gè)人只需關(guān)注自身組件業(yè)務(wù)開(kāi)發(fā),不用關(guān)心其他組件的具體實(shí)現(xiàn)細(xì)節(jié)。自身改動(dòng)也不用擔(dān)心影響其他模塊。也避免了各個(gè)業(yè)務(wù)代碼頻繁提交合并等操作以及可能帶來(lái)的問(wèn)題。
4、單元測(cè)試
縮小測(cè)試范圍,有利于問(wèn)題定位和解決。排除其他模塊可能帶來(lái)的影響。
注意:哪些項(xiàng)目不適合組件化
1、項(xiàng)目較小,模塊間交互簡(jiǎn)單,耦合少
2、模塊沒(méi)有被多個(gè)外部模塊引用,只是一個(gè)單獨(dú)的小模塊
3、模塊不需要重用,代碼也很少被修改
4、團(tuán)隊(duì)規(guī)模很小
二、如何組件化
1、首先進(jìn)行分層
客戶端APP主流框架都將應(yīng)用分成三層:
這三層主要有業(yè)務(wù)層、通用層和基礎(chǔ)層。
- 業(yè)務(wù)層。就是我們APP具體的業(yè)務(wù)。比如常見(jiàn)的首頁(yè)、發(fā)現(xiàn)、我的等模塊。
- 通用層。比如常用控件、數(shù)據(jù)管理、分享、三方登錄等等。
- 基礎(chǔ)層。比如網(wǎng)絡(luò)、數(shù)據(jù)庫(kù)等。
分層的時(shí)候需要注意一下幾個(gè)原則:
- 只能上層對(duì)下層依賴(lài)
比如業(yè)務(wù)層可以依賴(lài)通用層和基礎(chǔ)層,通用層也可以依賴(lài)基礎(chǔ)層,但是通用層和基礎(chǔ)層不能依賴(lài)業(yè)務(wù)層,基礎(chǔ)層也不能依賴(lài)通用層。 - 公共代碼下沉
當(dāng)我們遇到同層多個(gè)模塊都會(huì)引用的代碼時(shí),可以考慮代碼下沉,封裝成一個(gè)通用業(yè)務(wù)模塊,使得代碼可以被復(fù)用,提高效率。 - 減少橫向依賴(lài)、適當(dāng)下沉
同層間相互依賴(lài)可以適當(dāng)增加中間件(Mediator)進(jìn)行解耦,這樣中間件下沉,減少同層間直接依賴(lài)。
2、模塊劃分
模塊的劃分是一個(gè)比較難的點(diǎn)。除了要考慮業(yè)務(wù)本身,還要考慮到團(tuán)隊(duì)人員情況。要怎么劃分、劃分的粒度有多大要具體情況具體分析。
但是單從技術(shù)的角度考慮,模塊劃分應(yīng)該從基礎(chǔ)層開(kāi)始劃分。為什么呢?
- 首先,最上層通常是業(yè)務(wù)層,如果先從上層開(kāi)始劃分會(huì)直接影響到具體業(yè)務(wù),而且組件劃分不是一朝一夕能完成,中間肯定會(huì)影響業(yè)務(wù)的開(kāi)發(fā);
- 其次,按照大家的正常思維,上層依賴(lài)下層,上層的代碼依賴(lài)的代碼比較多,如果從上層開(kāi)始,就會(huì)出現(xiàn)減代碼的情況,會(huì)存在很多不可預(yù)估的風(fēng)險(xiǎn);
- 再次,從基礎(chǔ)層開(kāi)始的話,因?yàn)榛A(chǔ)層不涉及到具體業(yè)務(wù),也不影響業(yè)務(wù)開(kāi)發(fā)?;A(chǔ)層可以再開(kāi)發(fā)、測(cè)試完成確定穩(wěn)定可靠之后再讓上層代碼慢慢接入,這時(shí)候上層代碼在劃分的時(shí)候遇到對(duì)基礎(chǔ)的依賴(lài)就可以直接替換成新的基礎(chǔ)模塊,平滑進(jìn)行過(guò)度。
對(duì)于通用業(yè)務(wù)層的劃分,如果是比較確定的可以提前劃分好。有些模塊可能需要在業(yè)務(wù)層具體劃分的時(shí)候視情況增加。業(yè)務(wù)層的劃分就得具體情況具體分析了。模塊劃分時(shí)遇到交叉依賴(lài)問(wèn)題,適當(dāng)增加中間件解耦(Mediator),常用的解耦方式有:路由、target-action (CDMediator, invocation)和protocol classes。具體可以參考下面的常見(jiàn)組件化方案的比較。
常見(jiàn)組件化方案的比較
可以直接參考網(wǎng)友的一篇文章iOS組件化方案對(duì)比;
通過(guò)Cocoa Pod來(lái)管理組件
組件拆分之后通過(guò)Pod來(lái)管理,Pod創(chuàng)建私有庫(kù)參考使用Cocoapods開(kāi)發(fā)SDK;
通過(guò)代理方式實(shí)現(xiàn)組件化方案原理
這里的組件化方案只針對(duì)業(yè)務(wù)層和主app之間以及業(yè)務(wù)層和業(yè)務(wù)層之間組件化的方案。因?yàn)閷?duì)于基礎(chǔ)成和通用業(yè)務(wù)層,他們都屬于下層代碼,功能相對(duì)單一,層間依賴(lài)較少甚至,比較容易劃分,而且相對(duì)比較穩(wěn)定,業(yè)務(wù)層是可以通過(guò)各自的中間層或者直接依賴(lài)的。
代理方案實(shí)現(xiàn)組件化的三個(gè)要素
核心實(shí)現(xiàn)由三大部分組成:組件管理模塊、協(xié)議模塊和和各個(gè)業(yè)務(wù)組件模塊。主APP和組件以及組件間的通信通過(guò)代理來(lái)實(shí)現(xiàn)。首先組件管理模塊負(fù)責(zé)組件和協(xié)議的映射管理;協(xié)議模塊聲明了各個(gè)模塊提供給外部訪問(wèn)的代理方法;各個(gè)組件有對(duì)應(yīng)的代理實(shí)現(xiàn)了對(duì)應(yīng)協(xié)議。每個(gè)組件都要依賴(lài)組件管理模塊和協(xié)議模塊,動(dòng)態(tài)將各自模塊的協(xié)議和負(fù)責(zé)實(shí)現(xiàn)協(xié)議代理對(duì)象的類(lèi)名注冊(cè)到組件管理模塊的映射表中,同時(shí)可以根據(jù)需要可以通過(guò)協(xié)議模塊中其他組件的代理方法與其他組件進(jìn)行通信。
各組件的協(xié)議和實(shí)現(xiàn)協(xié)議的代理對(duì)象是任意的嗎?
為了便于管理,每個(gè)組件對(duì)外的協(xié)議都有一個(gè)專(zhuān)門(mén)的中介層來(lái)實(shí)現(xiàn)這個(gè)協(xié)議,這個(gè)中介層負(fù)責(zé)組件內(nèi)部和組件外部通信。這個(gè)中介層通常是一個(gè)對(duì)象,當(dāng)可能存在多個(gè)協(xié)議時(shí),可以通過(guò)分類(lèi)等方式進(jìn)行模塊化。這樣做的目的是為了減少組件間通信的復(fù)雜度,保持內(nèi)部對(duì)外通信的統(tǒng)一性。外部訪問(wèn)這個(gè)組件時(shí)只需要找到這個(gè)組件對(duì)應(yīng)的一個(gè)協(xié)議就可以了,而內(nèi)部只需要有一個(gè)專(zhuān)門(mén)的中介層來(lái)接收外部信息,并統(tǒng)一在組件內(nèi)部進(jìn)行調(diào)度,使得整個(gè)通信流程有法可依、有跡可循。
組件的協(xié)議和其實(shí)現(xiàn)類(lèi)是在什么時(shí)機(jī)注冊(cè)到映射表中的呢?
- 第一種方法可以在代理類(lèi)的+load方法中動(dòng)態(tài)注冊(cè),直接注冊(cè)代理對(duì)象和協(xié)議的映射關(guān)系,但是這樣做的代碼很多的話會(huì)影響啟動(dòng)速度。至于為什么會(huì)影響啟動(dòng)速度,這個(gè)問(wèn)題涉及到類(lèi)的加載流程,具體參考OC類(lèi)的加載流程;
@implementation LNFeedModule
+(void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[LNModuleManager sharedInstance] addImpClassName:@"LNFeedModule" protocolName:@"LNFeedModuleProtocol"];
});
}
實(shí)現(xiàn)+load方法會(huì)導(dǎo)致LNFeedModule稱(chēng)為非懶加載類(lèi),在程序啟動(dòng)是加載,消耗性能。
- 第二種方法是在C++構(gòu)造方法中注冊(cè), 這樣做雖然也會(huì)導(dǎo)致LNModuleManager被提前加載,但是LNModuleManager只會(huì)被加載一次,影響不大。而且本社注冊(cè)類(lèi)名和協(xié)議名稱(chēng)只不過(guò)是簡(jiǎn)單的字典操作,耗時(shí)較少,對(duì)啟動(dòng)性能影響不大,最重要的是實(shí)現(xiàn)簡(jiǎn)單。每個(gè)模塊實(shí)現(xiàn)自己的C++構(gòu)造方法即可。
//注冊(cè)類(lèi)名和協(xié)議名稱(chēng)
__attribute__((constructor)) void addModulLNFeedModule(void){
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[LNModuleManager sharedInstance] addImpClassName:@"LNFeedModule" protocolName:@"LNFeedModuleProtocol"];
});
}
- 第三種方法可以參考阿里的BeeHive組件化工具。利用注解在程序編譯時(shí)把對(duì)應(yīng)的協(xié)議的實(shí)現(xiàn)和協(xié)議寫(xiě)入mach-o文件中,待鏡像文件加載時(shí)讀取,性能較好。實(shí)現(xiàn)雖然復(fù)雜,但是也已經(jīng)封裝好了,可以直接用。
每個(gè)組件如何監(jiān)聽(tīng)APP的生命周期?
每個(gè)組件對(duì)應(yīng)的協(xié)議都會(huì)繼承UIAppDelegate協(xié)議,這樣每個(gè)組件都可以根據(jù)需要監(jiān)聽(tīng)?wèi)?yīng)用程序的代理方法,完成自身相關(guān)的操作。
總結(jié)
架構(gòu)設(shè)計(jì)的最終目的是為了提高效率。拋開(kāi)開(kāi)發(fā)人員自身的問(wèn)題,這個(gè)效率主要有幾個(gè)方面影響:其一、程序架構(gòu)是否簡(jiǎn)單易懂,越簡(jiǎn)單易懂,自然越容易保障開(kāi)發(fā)效率;其二、盡可能少寫(xiě)代碼,代碼復(fù)用率高,復(fù)用率高一定程度上能提高開(kāi)發(fā)效率;其三、專(zhuān)注于自身業(yè)務(wù),盡量的少關(guān)注其他人。組件化不要只是為了解耦而解耦,而是一切以是否有利于提高效率為目的。解耦應(yīng)該是為了可擴(kuò)展性、可維護(hù)性和可復(fù)用性等,但同時(shí)要兼顧可讀性和可靠性。