本文做者: 伯樂在線 - 劉小壯 。未經做者許可,禁止轉載!
歡迎加入伯樂在線 專欄做者。ios
前段時間公司項目打算重構,準確來講應該是按以前的產品邏輯重寫一個項目😂。在重構項目以前涉及到架構選型的問題,我和組裏小夥伴一塊兒研究了一下組件化架構,打算將項目重構爲組件化架構。固然不是直接拿來照搬,仍是要根據公司具體的業務需求設計架構。git
在學習組件化架構的過程當中,從不少高質量的博客中學到很多東西,例如蘑菇街李忠、casatwy、bang的博客。在學習過程當中也遇到一些問題,在微博和QQ上和一些作iOS
的朋友進行了交流,很是感謝這些朋友的幫助。github
本篇文章主要針對於以前蘑菇街提出的組件化方案,以及casatwy提出的組件化方案進行分析,後面還會簡單提到滴滴、淘寶、微信的組件化架構,最後會簡單說一下我公司設計的組件化架構。web
隨着移動互聯網的不斷髮展,不少程序代碼量和業務愈來愈多,現有架構已經不適合公司業務的發展速度了,不少都面臨着重構的問題。
在公司項目開發中,若是項目比較小,普通的單工程+MVC架構
就能夠知足大多數需求了。可是像淘寶、蘑菇街、微信這樣的大型項目,原有的單工程架構就不足以知足架構需求了。數據庫
就拿淘寶來講,淘寶在13年開啓的「All in 無線」
戰略中,就將阿里系大多數業務都加入到手機淘寶中,使客戶端出現了業務的爆發。在這種狀況下,單工程架構則已經遠遠不能知足現有業務需求了。因此在這種狀況下,淘寶在13年開啓了插件化架構的重構,後來在14年迎來了手機淘寶有史以來最大規模的重構,將其完全重構爲組件化架構。緩存
在一個項目愈來愈大,開發人員愈來愈多的狀況下,項目會遇到不少問題。安全
爲了解決上面的問題,能夠考慮加一個中間層來協調模塊間的調用,全部的模塊間的調用都會通過中間層中轉。(注意看兩張圖的箭頭方向)服務器
可是發現增長這個中間層後,耦合仍是存在的。中間層對被調用模塊存在耦合,其餘模塊也須要耦合中間層才能發起調用。這樣仍是存在以前的相互耦合的問題,並且本質上比以前更麻煩了。微信
因此應該作的是,只讓其餘模塊對中間層產生耦合關係,中間層不對其餘模塊發生耦合。
對於這個問題,能夠採用組件化的架構,將每一個模塊做爲一個組件。而且創建一個主項目,這個主項目負責集成全部組件。這樣帶來的好處是不少的:網絡
進行組件化開發後,能夠把每一個組件當作一個獨立的app,每一個組件甚至能夠採起不一樣的架構,例如分別使用MVVM
、MVC
、MVCS
等架構。
蘑菇街經過MGJRouter
實現中間層,經過MGJRouter
進行組件間的消息轉發,從名字上來講更像是路由器。實現方式大體是,在提供服務的組件中提早註冊block
,而後在調用方組件中經過URL
調用block
,下面是調用方式。
MGJRouter
是一個單例對象,在其內部維護着一個「URL -> block」
格式的註冊表,經過這個註冊表來保存服務方註冊的block
,以及使調用方能夠經過URL
映射出block
,並經過MGJRouter
對服務方發起調用。
在服務方組件中都對外提供一個接口類,在接口類內部實現block
的註冊工做,以及block
對外提供服務的代碼實現。每個block
都對應着一個URL
,調用方能夠經過URL
對block
發起調用。
在程序開始運行時,須要將全部服務方的接口類實例化,以完成這個註冊工做,使MGJRouter
中全部服務方的block
能夠正常提供服務。在這個服務註冊完成後,就能夠被調用方調起並提供服務。
蘑菇街項目使用git
做爲版本控制工具,將每一個組件都當作一個獨立工程,並創建主項目來集成全部組件。集成方式是在主項目中經過CocoaPods
來集成,將全部組件當作二方庫集成到項目中。詳細的集成技術點在下面「標準組件化架構設計」
章節中會講到。
代碼模擬對詳情頁的註冊、調用,在調用過程當中傳遞id
參數。下面是註冊的示例代碼:
1
2
3
4
|
[MGJRouter registerURLPattern:@"mgj://detail?id=id" toHandler:^(NSDictionary *routerParameters) {
// 下面能夠在拿到參數後,爲其餘組件提供對應的服務
NSString uid = routerParameters[@"id"];
}];
|
經過openURL:
方法傳入的URL
參數,對詳情頁已經註冊的block
方法發起調用。調用方式相似於GET
請求,URL
地址後面拼接參數。
1
|
[MGJRouter openURL:@"mgj://detail?id=404"];
|
也能夠經過字典方式傳參,MGJRouter
提供了帶有字典參數的方法,這樣就能夠傳遞非字符串以外的其餘類型參數。
1
|
[MGJRouter openURL:@"mgj://detail?" withParam:@{@"id" : @"404"}];
|
有的時候組件間調用過程當中,須要服務方在完成調用後返回相應的參數。蘑菇街提供了另外的方法,專門來完成這個操做。
1
2
3
|
[MGJRouter registerURLPattern:@"mgj://cart/ordercount" toObjectHandler:^id(NSDictionary *routerParamters){
return @42;
}];
|
經過下面的方式發起調用,並獲取服務方返回的返回值,要作的就是傳遞正確的URL
和參數便可。
1
|
NSNumber *orderCount = [MGJRouter objectForURL:@"mgj://cart/ordercount"];
|
這時候會發現一個問題,在蘑菇街組件化架構中,存在了不少硬編碼的URL和參數。在代碼實現過程當中URL
編寫出錯會致使調用失敗,並且參數是一個字典類型,調用方不知道服務方須要哪些參數,這些都是個問題。
對於這些數據的管理,蘑菇街開發了一個web
頁面,這個web
頁面統一來管理全部的URL
和參數,Android
和iOS
都使用這一套URL
,能夠保持統一性。
在項目中存在不少公共部分的東西,例如封裝的網絡請求、緩存、數據處理等功能,以及項目中所用到的資源文件。
蘑菇街將這些部分也當作組件,劃分爲基礎組件,位於業務組件下層。全部業務組件都使用同一個基礎組件,也能夠保證公共部分的統一性。
爲了解決MGJRouter
方案中URL
硬編碼,以及字典參數類型不明確等問題,蘑菇街在原有組件化方案的基礎上推出了Protocol
方案。Protocol
方案由兩部分組成,進行組件間通訊的ModuleManager
類以及MGJComponentProtocol
協議類。
經過中間件ModuleManager
進行消息的調用轉發,在ModuleManager
內部維護一張映射表,映射表由以前的"URL -> block"
變成"Protocol -> Class"
。
在中間件中建立MGJComponentProtocol
文件,服務方組件將能夠用來調用的方法都定義在Protocol
中,將全部服務方的Protocol
都分別定義到MGJComponentProtocol
文件中,若是協議比較多也能夠分開幾個文件定義。這樣全部調用方依然是隻依賴中間件,不須要依賴除中間件以外的其餘組件。
Protocol
方案中每一個組件也須要一個「接口類」,此類負責實現當前組件對應的協議方法,也就是對外提供服務的實現。在程序開始運行時將自身的Class
註冊到ModuleManager
中,並將Protocol
反射出字符串當作key
。這個註冊過程和MGJRouter
是相似的,都須要提早註冊服務。
建立MGJUserImpl
類當作User
模塊的服務類,並在MGJComponentProtocol.h
中定義MGJUserProtocol
協議,由MGJUserImpl
類實現協議中定義的方法,完成對外提供服務的過程。下面是協議定義:
1
2
3
|
@protocol MGJUserProtocol
- (NSString *)getUserName;
@end
|
Class
遵照協議並實現定義的方法,外界經過Protocol
獲取的Class
實例化爲對象,調用服務方實現的協議方法。
ModuleManager
的協議註冊方法,註冊時將Protocol
反射爲字符串當作存儲的key
,將實現協議的Class
當作值存儲。經過Protocol
取Class
的時候,就是經過Protocol
從ModuleManager
中將Class
映射出來。
1
|
[ModuleManager registerClass:MGJUserImpl forProtocol:@protocol(MGJUserProtocol)];
|
調用時經過Protocol
從ModuleManager
中映射出註冊的Class
,將獲取到的Class
實例化,並調用Class
實現的協議方法完成服務調用。
1
2
3
|
Class cls = [[ModuleManager sharedInstance] classForProtocol:@protocol(MGJUserProtocol)];
id userComponent = [[cls alloc] init];
NSString *userName = [userComponent getUserName];
|
蘑菇街是OpenURL
和Protocol
混用的方式,兩種實現的調用方式不一樣,但大致調用邏輯和實現思路相似,因此下面的調用流程兩者差很少。在OpenURL
不能知足需求或調用不方便時,就能夠經過Protocol
的方式調用。
MGJRouter
對服務方組件進行註冊。每一個URL
對應一個block
的實現,block
中的代碼就是服務方對外提供的服務,調用方能夠經過URL
調用這個服務。MGJRouter
調用openURL:
方法,並將被調用代碼對應的URL
傳入,MGJRouter
會根據URL
查找對應的block
實現,從而調用服務方組件的代碼進行通訊。block
時,block
有一個字典用來傳遞參數。這樣的優點就是參數類型和數量理論上是不受限制的,可是須要不少硬編碼的key
名在項目中。蘑菇街組件化方案有兩種,Protocol
和MGJRouter
的方式,但都須要進行register
操做。Protocol
註冊的是Class
,MGJRouter
註冊的是Block
,註冊表是一個NSMutableDictionary
類型的字典,而字典的擁有者又是一個單例對象,這樣會形成內存的常駐。
下面是對兩種實現方式內存消耗的分析:
block
實現方式可能致使的內存問題,block
若是使用不當,很容易形成循環引用的問題。block
對象,而block
對象自己並不會佔用多少內存。在調用block
後會對block
體中的方法進行執行,執行完成後block
體中的對象釋放。block
自身的實現只是一個結構體,也就至關於字典中存放的是不少結構體,因此內存的佔用並非很大。block
內存常駐方式差很少。只是將存儲的block
對象換成Class
對象,若是不是已經實例化的對象,內存佔用仍是比較小的。casatwy組件化方案分爲兩種調用方式,遠程調用和本地調用,對於兩個不一樣的調用方式分別對應兩個接口。
AppDelegate
代理方法傳遞到當前應用後,調用遠程接口並在內部作一些處理,處理完成後會在遠程接口內部調用本地接口,以實現本地調用爲遠程調用服務。performTarget:action:params:
方法負責,但調用方通常不直接調用performTarget:
方法。CTMediator
會對外提供明確參數和方法名的方法,在方法內部調用performTarget:
方法和參數的轉換。casatwy是經過CTMediator
類實現組件化的,在此類中對外提供明確參數類型的接口,接口內部經過performTarget
方法調用服務方組件的Target
、Action
。因爲CTMediator
類的調用是經過runtime
主動發現服務的,因此服務方對此類是徹底解耦的。
但若是CTMediator
類對外提供的方法都放在此類中,將會對CTMediator
形成極大的負擔和代碼量。解決方法就是對每一個服務方組件建立一個CTMediator
的Category
,並將對服務方的performTarget
調用放在對應的Category
中,這些Category
都屬於CTMediator
中間件,從而實現了感官上的接口分離。
對於服務方的組件來講,每一個組件都提供一個或多個Target
類,在Target
類中聲明Action
方法。Target
類是當前組件對外提供的一個「服務類」,Target
將當前組件中全部的服務都定義在裏面,CTMediator
經過runtime
主動發現服務。
在Target
中的全部Action
方法,都只有一個字典參數,因此能夠傳遞的參數很靈活,這也是casatwy提出的去Model
化的概念。在Action
的方法實現中,對傳進來的字典參數進行解析,再調用組件內部的類和方法。
casatwy爲咱們提供了一個Demo,經過這個Demo
能夠很好的理解casatwy的設計思路,下面按照個人理解講解一下這個Demo
。
打開Demo
後能夠看到文件目錄很是清楚,在上圖中用藍框框出來的就是中間件部分,紅框框出來的就是業務組件部分。我對每一個文件夾作了一個簡單的註釋,包含了其在架構中的職責。
在CTMediator
中定義遠程調用和本地調用的兩個方法,其餘業務相關的調用由Category
完成。
1
2
3
4
|
// 遠程App調用入口
- (id)performActionWithUrl:(NSURL *)url completion:(void(^)(NSDictionary *info))completion;
// 本地組件調用入口
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params;
|
在CTMediator
中定義的ModuleA
的Category
,對外提供了一個獲取控制器並跳轉的功能,下面是代碼實現。因爲casatwy的方案中使用performTarget
的方式進行調用,因此涉及到不少硬編碼字符串的問題,casatwy採起定義常量字符串來解決這個問題,這樣管理也更方便。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
#import "CTMediator+CTMediatorModuleAActions.h"
NSString * const kCTMediatorTargetA = @"A";
NSString * const kCTMediatorActionNativFetchDetailViewController = @"nativeFetchDetailViewController";
@implementation CTMediator (CTMediatorModuleAActions)
- (UIViewController *)CTMediator_viewControllerForDetail {
UIViewController *viewController = [self performTarget:kCTMediatorTargetA
action:kCTMediatorActionNativFetchDetailViewController
params:@{@"key":@"value"}];
if ([viewController isKindOfClass:[UIViewController class]]) {
// view controller 交付出去以後,能夠由外界選擇是push仍是present
return viewController;
} else {
// 這裏處理異常場景,具體如何處理取決於產品
return [[UIViewController alloc] init];
}
}
|
下面是ModuleA
組件中提供的服務,被定義在Target_A
類中,這些服務能夠被CTMediator
經過runtime
的方式調用,這個過程就叫作發現服務。
咱們發現,在這個方法中其實作了參數處理和內部調用的功能,這樣就能夠保證組件內部的業務不受外部影響,對內部業務沒有侵入性。
1
2
3
4
5
6
|
- (UIViewController *)Action_nativeFetchDetailViewController:(NSDictionary *)params {
// 對傳過來的字典參數進行解析,並調用ModuleA內部的代碼
DemoModuleADetailViewController *viewController = [[DemoModuleADetailViewController alloc] init];
viewController.valueLabel.text = params[@"key"];
return viewController;
}
|
在大型項目中代碼量比較大,須要避免命名衝突的問題。對於這個問題casatwy採起的是加前綴的方式,從casatwy的Demo
中也能夠看出,其組件ModuleA
的Target
命名爲Target_A
,被調用的Action
命名爲Action_nativeFetchDetailViewController:
。
casatwy將類和方法的命名,都統一按照其功能作區分當作前綴,這樣很好的將組件相關和組件內部代碼進行了劃分。
這個章節叫作「標準組件化架構設計」,對於項目架構來講並無絕對意義的標準之說。這裏說到的「標準組件化架構設計」只是由於採起這樣的方式的人比較多,且這種方式相比而言較合理。
在上面文章中提到了casatwy方案的CTMediator
,蘑菇街方案的MGJRouter
和ModuleManager
,下面統稱爲中間件。
組件化架構中,首先有一個主工程,主工程負責集成全部組件。每一個組件都是一個單獨的工程,建立不一樣的git
私有倉庫來管理,每一個組件都有對應的開發人員負責開發。開發人員只須要關注與其相關組件的代碼,其餘業務代碼和其無關,來新人也好上手。
組件的劃分須要注意組件粒度,粒度根據業務可大可小。組件劃分後屬於業務組件,對於一些多個組件共同的東西,例如網絡、數據庫之類的,應該劃分到單獨的組件或基礎組件中。對於圖片或配置表這樣的資源文件,應該再單獨劃分一個資源組件,這樣避免資源的重複性。
服務方組件對外提供服務,由中間件調用或發現服務,服務對當前組件無侵入性,只負責對傳遞過來的數據進行解析和組件內調用的功能。須要被其餘組件調用的組件都是服務方,服務方也能夠調用其餘組件的服務。
經過這樣的組件劃分,組件的開發進度不會受其餘業務的影響,能夠多個組件單獨的並行開發。組件間的通訊都交給中間件來進行,須要通訊的類只須要接觸中間件,而中間件不須要耦合其餘組件,這就實現了組件間的解耦。中間件負責處理全部組件之間的調度,在全部組件之間起到控制核心的做用。
這套框架清晰的劃分了不一樣組件,從總體架構上來約束開發人員進行組件化開發,避免某個開發人員偷懶直接引用頭文件,產生組件間的耦合,破壞總體架構。假設之後某個業務發生大的改變,須要對相關代碼進行重構,能夠在單個組件進行重構。組件化架構下降了重構的風險,保證了代碼的健壯性。
每一個組件都是一個單獨的工程,在組件開發完成後上傳到git
倉庫。主工程經過Cocoapods
集成各個組件,集成和更新組件時只須要pod update
便可。這樣就是把每一個組件當作第三方來管理,管理起來很是方便。
Cocoapods
能夠控制每一個組件的版本,例如在主項目中回滾某個組件到特定版本,就能夠經過修改podfile
文件實現。選擇Cocoapods
主要由於其自己功能很強大,能夠很方便的集成整個項目,也有利於代碼的複用。經過這種集成方式,能夠很好的避免在傳統項目中代碼衝突的問題。
對於組件化架構的集成方式,我在看完bang的博客後專門請教了一下bang。根據在微博上和bang的聊天以及其餘博客中的學習,在主項目中集成組件主要分爲兩種方式——源碼和framework
,但都是經過CocoaPods
來集成。
不管是用CocoaPods
管理源碼,仍是直接管理framework
,效果都是同樣的,都是能夠直接進行pod update
之類的操做的。
這兩種組件集成方案,實踐中也是各有利弊。直接在主工程中集成代碼文件,能夠在主工程中進行調試。集成framework
的方式,能夠加快編譯速度,並且對每一個組件的代碼有很好的保密性。若是公司對代碼安全比較看重,能夠考慮framework
的形式,但framework
不利於主工程中的調試。
例如手機QQ或者支付寶這樣的大型程序,通常都會採起framework
的形式。並且通常這樣的大公司,都會有本身的組件庫,這個組件庫每每能夠表明一個大的功能或業務組件,直接添加項目中就可使用。關於組件化庫在後面講淘寶組件化架構的時候會提到。
以前有些項目是直接用workspace
的方式集成的,或者直接在原有項目中創建子項目,直接作文件引用。但這兩點都是不建議作的,由於沒有真正意義上實現業務組件的剝離,只是像以前的項目同樣從文件目錄結構上進行了劃分。
對於項目架構來講,必定要創建於業務之上來設計架構。不一樣的項目業務不一樣,組件化方案的設計也會不一樣,應該設計最適合公司業務的架構。
在除蘑菇街Protocol
方案外,其餘兩種方案都或多或少的存在硬編碼問題,硬編碼若是量比較大的話挺麻煩的。
在casatwy的CTMediator
方案中須要硬編碼Target
、Action
字符串,只不過這個缺陷被封閉在中間件裏面了,將這些字符串都統必定義爲常量,外界使用不須要接觸到硬編碼。蘑菇街的MGJRouter
的方案也是同樣的,也有硬編碼URL
的問題,蘑菇街可能也作了相似的處理。
casatwy和蘑菇街提出的兩套組件化方案,大致結構是相似的,三套方案都分爲調用方、中間件、服務方,只是在具體實現過程當中有些不一樣。例如Protocol
方案在中間件中加入了Protocol
文件,casatwy的方案在中間件中加入了Category
。
三種方案內部都有容錯處理,因此三種方案的穩定性都是比較好的,並且均可以拿出來單獨運行,在服務方不存在的狀況下也不會有問題。
在三套方案中,服務方都對外提供一個供外界調用的接口類,這個類中實現組件對外提供的服務,中間件經過接口類來實現組件間的通訊。在此類中統必定義對外提供的服務,外界調用時就知道服務方能夠作什麼。
調用流程也不大同樣,蘑菇街的兩套方案都須要註冊操做,不管是Block
仍是Protocol
都須要註冊後才能夠提供服務。而casatwy的方案則不須要,直接經過runtime
調用。casatwy的方案實現了真正的對服務方解耦,而蘑菇街的兩套方案則沒有,對服務方和調用方都形成了耦合。
我認爲三套方案中,Protocol
方案是調用和維護最麻煩的一套方案。維護時須要同時維護Protocol
、接口類兩部分。並且調用時須要將服務方的接口類返回給調用方,並由調用方執行一系列調用邏輯,調用一個服務的邏輯很是複雜,這在開發中是很是影響開發效率的。
MGJRouter
方案中,是經過調用OpenURL:
方法並傳入URL
來發起調用。鑑於URL
協議名等固定格式,能夠經過判斷協議名的方式,使用配置表控制H5
和native
的切換,配置表能夠從後臺更新,只須要將協議名更改一下便可。
mgj://detail?id=123456
http://www.mogujie.com/detail?id=123456
假設如今線上的native
組件出現嚴重bug
,在後臺將配置文件中原有的本地URL
換成H5
的URL
,並更新客戶端配置文件。在調用MGJRouter
時傳入這個H5
的URL
便可完成切換,MGJRouter
判斷若是傳進來的是一個H5
的URL
就直接跳轉webView
。並且URL
能夠傳遞參數給MGJRouter
,只須要MGJRouter
內部作參數截取便可。
Protocol
方案,都提供了傳遞明確類型參數的方法。在MGJRouter
方案中,傳遞參數主要是經過相似GET
請求同樣在URL
後面拼接參數,和在字典中傳遞參數兩種方式組成。這兩種方式會形成傳遞參數類型不明確,傳遞參數類型受限(GET
請求不能傳遞對象)等問題,後來使用Protocol
方案彌補這個問題。下面就簡單說說我公司項目架構,公司項目是一個地圖導航應用,業務層之下的基礎組件佔比較大。且基礎組件相對比較獨立,對外提供了不少調用接口。剛開始想的是採用MGJRouter
的方案,但若是這些調用都經過Router
進行,開發起來比較複雜,反而會拔苗助長。最主要咱們項目也並非很是大,不必都用Router
轉發。
對於這個問題,公司項目的架構設計是:層級架構+組件化架構,組件化架構處於層級架構的最上層,也就是業務層。採起這種結構混合的方式進行總體架構,這個對於公共組件的管理和層級劃分比較有利,符合公司業務需求。
對於業務層級依然採用組件化架構的設計,這樣能夠充分利用組件化架構的優點,對項目組件間進行解耦。在上層和下層的調用中,下層的功能組件應該對外開放一個接口類,在接口類中聲明全部的服務,實現上層調用當前組件的一箇中轉,上層直接調用接口類。這樣作的好處在於,若是下層發生改變不會對上層形成影響,並且也省去了部分Router
轉發的工做。
在設計層級架構時,須要注意只能上層對下層依賴,下層對上層不能有依賴,下層中不要包含上層業務邏輯。對於項目中存在的公共資源和代碼,應該將其下沉到下層中。
首先就像我剛纔說的,我公司項目並非很大,根本不必拆分的那麼完全。
由於組件化開發有一個很重要的緣由就是解耦合,若是我作到了底層不對上層依賴,這樣就已經解除了上下層的相互耦合。並且上層對下層進行調用的時候,也不是直接調用下層,經過一個接口類進行中轉,實現了下層的改變對上層無影響,這也是上層對下層解耦的表現。
因此對於第三方就不用說了,上層直接調用下層的第三方也是沒問題的,這都是解耦的。
casatwy對模型類的觀點是去Model化,簡單來講就是用字典代替Model
存儲數據。這對於組件化架構來講,是解決組件之間數據傳遞的一個很好的方法。
由於模型類是關乎業務的,理論上必須放在業務層也就是業務組件這一層。可是要把模型對象從一個組件中當作參數傳遞到另外一個組件中,模型類放在調用方和服務方的哪一個組件都不太合適,並且有可能不僅兩個組件使用到這個模型對象。這樣的話在其餘組件使用模型對象,必然會形成引用和耦合。
那麼若是把模型類放在Router
中,這樣會形成Router
耦合了業務,形成業務的侵入性。若是在用到這個模型對象的全部組件中,都分別維護一份相同的模型類,這樣以後業務發生改變模型類就會很麻煩。
若是將模型類單獨拉出來,定義一個模型組件呢?這個看起來比較可行,將這個定義模型的組件下沉到下層,模型組件不包含業務,只聲明模型對象的類。可是通常組件的模型對象都是當前組件內使用的,將模型對象傳遞給其餘組件的需求很是少,那全部的模型類都定義到模型組件嗎?
對於這個問題,我建議在項目開發中將模型類還定義在當前業務組件中,在組件間傳遞模型對象時進行去Model化,傳遞字典類型的參數。
上面只是思考,恰巧我公司持久化方案用的是CoreData
,全部模型的定義都在CoreData
組件中,這樣就避免了業務層組件之間由於模型類的耦合。
以前看過滴滴iOS
負責人李賢輝的技術分享,分享的是滴滴iOS
客戶端的架構發展歷程,下面簡單總結一下。
滴滴在最開始的時候架構較混亂。而後在2.0時期重構爲MVC
架構,使項目劃分更加清晰。在3.0時期上線了新的業務線,這時採用的遊戲開發中的狀態機機制,暫時能夠知足現有業務。
然而在後期不斷上線順風車、代駕、巴士等多條業務線的狀況下,現有架構變得很是臃腫,代碼耦合嚴重。從而在2015年開始了代號爲「The One」
的方案,這套方案就是滴滴的組件化方案。
滴滴的組件化方案,和蘑菇街方案相似,也是經過私有CocoaPods
來管理各個組件。將整個項目拆分爲業務部分和技術部分,業務部分包括專車、拼車、巴士等業務模塊,每一個業務模塊就是一個單獨的組件,使用一個pods
管理。技術部分則分爲登陸分享、網絡、緩存這樣的一些基礎組件,分別使用不一樣的pods
管理。
組件間通訊經過ONERouter
中間件進行通訊,ONERouter
相似於MGJRouter
,擔負起協調和調用各個組件的做用。組件間通訊經過OpenURL
方法,來進行對應的調用。ONERouter
內部保存一份Class-URL
的映射表,經過URL
找到Class
併發起調用,Class
的註冊放在+load
方法中進行。
滴滴在組件內部的業務模塊中,模塊內部使用MVVM+MVCS
混合架構,兩種架構都是MVC
的衍生版本。其中MVCS
中的Store
負責數據相關邏輯,例如訂單狀態、地址管理等數據處理。經過MVVM
中的VM
給控制器瘦身,最後Controller
的代碼量就不多了。
滴滴文章中說道首頁只能有一個地圖實例,這在不少地圖導航相關應用中都是這樣作的。滴滴首頁主控制器持有導航欄和地圖,每一個業務線首頁控制器都添加在主控制器上,而且業務線控制器背景都設置爲透明,將透明部分響應事件傳遞到下面的地圖中,只響應屬於本身的響應事件。
由主控制器來切換各個業務線首頁,切換頁面後根據不一樣的業務線來更新地圖數據。
本章節源自於宗心在阿里技術沙龍上的一次分享
淘寶iOS
客戶端初期是單工程的普通項目,但隨着業務的飛速發展,現有架構並不能承載愈來愈多的業務需求,致使代碼間耦合很嚴重。後期開發團隊對其不斷進行重構,淘寶iOS
和Android
兩個平臺,除了某個平臺特有的一些特性或某些方案不便實施以外,大致架構都是差很少的。
MVC
架構進行開發。隨着業務不斷的增長,致使項目很是臃腫、耦合嚴重。framework
二方庫的形式集成到主工程。但這種方式並無作到真正的拆分,仍是在一個工程中使用git
進行merge
,這樣還會形成合並衝突、很差回退等問題。framework
。主工程經過podfile
集成全部組件framework
,實現業務之間真正的隔離,經過CocoaPods
實現組件化架構。淘寶是使用git
來作源碼管理的,在插件化架構時須要儘量避免merge
操做,不然在大團隊中協做成本是很大的。而使用CocoaPods
進行組件化開發,則避免了這個問題。
在CocoaPods
中能夠經過podfile
很好的配置各個組件,包括組件的增長和刪除,以及控制某個組件的版本。使用CocoaPods
的緣由,很大程度是爲了解決大型項目中,代碼管理工具merge
代碼致使的衝突。而且能夠經過配置podfile
文件,輕鬆配置項目。
每一個組件工程有兩個target
,一個負責編譯當前組件和運行調試,另外一個負責打包framework
。先在組件工程作測試,測試完成後再集成到主工程中集成測試。
每一個組件都是一個獨立app
,能夠獨立開發、測試,使得業務組件更加獨立,全部組件能夠並行開發。下層爲上層提供能知足需求的底層庫,保證上層業務層能夠正常開發,並將底層庫封裝成framework
集成到項目中。
使用CocoaPods
進行組件集成的好處在於,在集成測試本身組件時,能夠直接將本地主工程podfile
文件中的當前組件指向本地,就能夠直接進行集成測試,不須要提交到服務器倉庫。
淘寶架構的核心思想是一切皆組件,將工程中全部代碼都抽象爲組件。
淘寶架構主要分爲四層,最上層是組件Bundle
(業務組件),依次往下是容器(核心層),中間件Bundle
(功能封裝),基礎庫Bundle
(底層庫)。容器層爲整個架構的核心,負責組件間的調度和消息派發。
總線設計:URL
路由+服務+消息。統一全部組件的通訊標準,各個業務間經過總線進行通訊。
URL
能夠請求也能夠接受返回值,和MGJRouter
差很少。URL
路由請求能夠被解析就直接拿來使用,若是不能被解析就跳轉H5
頁面。這樣就完成了一個對不存在組件調用的兼容,使用戶手中比較老的版本依然能夠顯示新的組件。
服務提供一些公共服務,由服務方組件負責實現,經過Protocol
實現。消息負責統一發送消息,相似於通知也須要註冊。
淘寶提出Bundle App
的概念,能夠經過已有組件,進行簡單配置後就能夠組成一個新的app
出來。解決了多個應用業務複用的問題,防止重複開發同一業務或功能。
Bundle
即App
,容器即OS
,全部Bundle App
被集成到OS
上,使每一個組件的開發就像app
開發同樣簡單。這樣就作到了從巨型app
迴歸普通app
的輕盈,使大型項目的開發問題完全獲得瞭解決。
到目前爲止組件化架構文章就寫完了,文章確實挺長的,看到這裏真是辛苦你了😁。下面留個小思考,把下面字符串複製到微信輸入框隨便發給一個好友,而後點擊下面連接大概也能猜到微信的組件化方案。
1
|
weixin://dl/profile
|
各位能夠來我博客評論區討論,能夠討論文中提到的技術細節,也能夠討論本身公司架構所遇到的問題,或本身獨到的看法等等。不管是否是架構師或新入行的iOS開發,歡迎各位以一個討論技術的心態來討論。在評論區你的問題能夠被其餘人看到,這樣可能會給其餘人帶來一些啓發。
如今H5
技術比較火,好多應用都用H5
來完成一些頁面的開發,H5
的跨平臺和實時更新等是很是大的優勢,但其性能和交互也是缺點。若是之後客戶端可以發展到能夠動態部署線上代碼,不用打包上線應用市場,直接就能夠作到原生應用更新,這樣就能夠解決原生應用最大的痛點。這段時間公司項目比較忙,有時間我打算研究一下這個技術點😄。
Demo
地址:蘑菇街和casatwy
組件化方案,其Github
上都給出了Demo
,這裏就貼出其Github
地址了。