前言
當(dāng)一個(gè)App只有幾個(gè)人開(kāi)發(fā)的時(shí)候,很容易就會(huì)在一個(gè)單項(xiàng)目中開(kāi)發(fā)。但當(dāng)App開(kāi)發(fā)人數(shù)越來(lái)越多,甚至幾百人,十幾個(gè)不同BU都在協(xié)調(diào)開(kāi)發(fā)同一個(gè)App的時(shí)候,就必須對(duì)架構(gòu)進(jìn)行組件化,才能方便開(kāi)發(fā)。本文主要基于手機(jī)淘寶的一次架構(gòu)探索:手機(jī)淘寶客戶端架構(gòu)探索實(shí)踐,基于此文進(jìn)行的一些學(xué)習(xí)和探索,寫一篇文章給自己梳理一下。
組件化的目的
首先,第一個(gè)問(wèn)題,為何需要組件化?
如果依舊是單工程項(xiàng)目,或者是多工程引入同一個(gè)項(xiàng)目的開(kāi)發(fā),會(huì)有以下的問(wèn)題:
- 嚴(yán)重的代碼耦合
比如a模塊要跳轉(zhuǎn)b模塊的頁(yè)面,就要在a模塊的代碼中耦合b模塊的頁(yè)面代碼 - 協(xié)同工作困難
開(kāi)發(fā)工程中需要去編譯別的模塊的代碼,還容易出現(xiàn)沖突問(wèn)題,引發(fā)別的問(wèn)題 - 測(cè)試效率低下
不僅測(cè)試某一個(gè)功能可能需要耦合別的模塊代碼,做回歸測(cè)試的時(shí)候業(yè)務(wù)也太多時(shí)分麻煩 - 發(fā)布不夠靈活
出現(xiàn)問(wèn)題定位麻煩,線上熱更新也困難
為了解決這些問(wèn)題,所以需要重構(gòu)代碼,達(dá)到組件化的目的。
組件化的方式
手機(jī)淘寶組件化兩大核心:
- 分而治之
- 一些皆組件
拿一張ppt里的圖:

可以看到,手機(jī)淘寶將業(yè)務(wù)都進(jìn)行細(xì)粒度的拆分,拆分出的每一個(gè)部分都作為一個(gè)組件。每個(gè)組件可以單獨(dú)進(jìn)行測(cè)試與調(diào)試,并且確保了單一的功能性,方便在新業(yè)務(wù)接入的時(shí)候,可以按照需求選擇相應(yīng)的組件來(lái)進(jìn)行添加。

對(duì)于bundle如何組合,這里用的是CocoaPods。
CocoaPods是一個(gè)著名的iOS類庫(kù)管理工具。這里就不詳細(xì)介紹如何安裝與用CocoaPods了,感興趣的可以自己去CocoaPods Guide去看一下。
通過(guò)CocoaPods可以十分方便的選擇需要的bundle包進(jìn)入項(xiàng)目,并且最關(guān)鍵的是可以控制bundle包的版本號(hào),選擇穩(wěn)定的舊版本或者新功能的老版本,避免了協(xié)同開(kāi)發(fā)的時(shí)候,可能出現(xiàn)的外部問(wèn)題,方便開(kāi)發(fā)與測(cè)試。
組件化核心層
從前面“手機(jī)淘寶組件化”的圖可以看出,組件化的核心層其實(shí)是容器層。
容器層主要分為兩大塊
- 應(yīng)用生命周期
- 總線
而總線,就是核心組件之間的解耦的關(guān)鍵。
總線主要分為三塊:
- URL
- 服務(wù)
- 消息
URL
URL應(yīng)該是整個(gè)總線傳輸?shù)暮诵摹?/strong>
模塊通過(guò)URL跳轉(zhuǎn)的的方式,來(lái)進(jìn)行模塊之間的消息傳遞。URL的用處是最多的,比如獲取相應(yīng)對(duì)象,跳轉(zhuǎn)相應(yīng)頁(yè)面,或者發(fā)起請(qǐng)求,都可以使用URL來(lái)進(jìn)行。
這里拿蘑菇街URL跳轉(zhuǎn)的demo:MGJRouter,來(lái)進(jìn)行舉例子。
MGJRouter主要就是通過(guò)各個(gè)模塊注冊(cè)相應(yīng)的URL跳轉(zhuǎn)block,建立起一層URL與block的映射關(guān)系,然后調(diào)用方通過(guò)URL去訪問(wèn)block,獲得結(jié)果。
通過(guò)URL的方式,調(diào)用方不需要去依賴其他模塊代碼,只需要直接調(diào)用,或者獲取相應(yīng)的結(jié)果進(jìn)行處理。
比如常見(jiàn)的頁(yè)面跳轉(zhuǎn)問(wèn)題,通過(guò)URL路由,就可以直接跳轉(zhuǎn)到相應(yīng)的頁(yè)面。
首先是注冊(cè)相應(yīng)的URL內(nèi)容,這里是詳情頁(yè)的內(nèi)容。
[MGJRouter registerURLPattern:@"mgj://detail?id=:id" toHandler:^(NSDictionary *routerParameters) {
NSNumber *id = routerParameters[@"id"];
// create view controller with id
// push view controller
}];
然后調(diào)用方只需要調(diào)用
[MGJRouter openURL:@"mgj://detail?id=404"];
就可以打開(kāi)相應(yīng)的界面。
不僅可以通過(guò)openURL的方式去打開(kāi)一個(gè)URL,也可以通過(guò)objectForUrl去通過(guò)URL獲取一個(gè)對(duì)象,然后進(jìn)行操作。
服務(wù)
服務(wù)用來(lái)彌補(bǔ)URL無(wú)法處理或者難以處理的功能。
服務(wù)的作用主要體現(xiàn)在一些組件之間的功能調(diào)用,會(huì)比URL更佳通用。比如登陸,購(gòu)物車等模塊的常用功能。
服務(wù)主要通過(guò)ModuleManager,去注冊(cè)Protocol->Class的關(guān)系,獲得相應(yīng)的對(duì)象,進(jìn)行Protocol的方法調(diào)用。
比如登陸組件可以提供這樣的一個(gè)Protocol
@protocol User <NSObject>
+ (NSString *)getUserName;
@end
可以看到通過(guò)協(xié)議可以直接指定返回的數(shù)據(jù)類型。然后在登陸組件內(nèi)再新建個(gè)類實(shí)現(xiàn)這個(gè)協(xié)議,假設(shè)這個(gè)類名為UserImp,接著就可以把它與協(xié)議關(guān)聯(lián)起來(lái)
[ModuleManager registerClass:UserImp forProtocol:@protocol(User)];
對(duì)于使用方來(lái)說(shuō),要拿到這個(gè) UserImp,需要調(diào)用
Class cls = [ModuleManager classForProtocol:@protocol(User)];
拿到之后再調(diào)用
id<User> userComponent = [[cls alloc] init];
NSString *userName = [userComponent getUserName];
就可以獲得用戶的用戶名了。
消息
消息就是常見(jiàn)的NSNotification相關(guān)的消息轉(zhuǎn)發(fā)機(jī)制,在這里做一個(gè)消息的統(tǒng)一管理和分發(fā)給各個(gè)模塊,各個(gè)模塊自己去處理響應(yīng)的消息。
總線總結(jié)
總線的主要目的就是組件與組件之間的消息傳輸?shù)慕怦睢?br>
URL是總線中最主要的使用場(chǎng)景,包括頁(yè)面調(diào)用與發(fā)起請(qǐng)求等功能。
服務(wù)是組件間的調(diào)用方式。需要服務(wù)提供方提供與維護(hù)穩(wěn)定的服務(wù)接口。
消息是在總線中進(jìn)行統(tǒng)一管理與分發(fā)。
對(duì)于URL為核心的總線機(jī)制有以下好處:
- 平臺(tái)統(tǒng)一
iOS,Android通過(guò)同一個(gè)URL總線在后臺(tái)進(jìn)行管理與配置。 - 自動(dòng)降級(jí)
老版本解析不了URL,走老的邏輯依舊可用。新版本可以解析URL,走新的邏輯。 - 中心分發(fā)
業(yè)務(wù)方分別注冊(cè)自己的URL攔截規(guī)則,配置在自己的模塊中。通過(guò)總線來(lái)中心分發(fā)響應(yīng)能夠響應(yīng)的模塊進(jìn)行處理。
結(jié)束語(yǔ)
本文主要還是根據(jù)手機(jī)淘寶客戶端架構(gòu)探索實(shí)踐和視頻,借鑒了蘑菇街 App 的組件化之路一些具體的實(shí)現(xiàn)思路,自己整理一下思路的總結(jié)和學(xué)習(xí)。
參考資料
1.手機(jī)淘寶客戶端架構(gòu)探索實(shí)踐
2.組件化架構(gòu)漫談
3.蘑菇街 App 的組件化之路
4.iOS應(yīng)用架構(gòu)談 組件化方案