iOS通過代理方式實現組件化方案的探究

一、為什么組件化

1、模塊間解耦

每個模塊盡可能可以進行獨立開發、調試、測試、打包等,模塊內部改動不影響或盡量少影響主APP和其他模塊;

2、模塊重用

模塊解耦實際上也是為了更好的復用。一個模塊可能會被多個模塊或者其他app所使用的,那么組件化把模塊獨立出來,方便復用。

3、提高團隊協作開發效率

組件化使得團隊成員分工更加明確,各自負責各自的業務線。每個人只需關注自身組件業務開發,不用關心其他組件的具體實現細節。自身改動也不用擔心影響其他模塊。也避免了各個業務代碼頻繁提交合并等操作以及可能帶來的問題。

4、單元測試

縮小測試范圍,有利于問題定位和解決。排除其他模塊可能帶來的影響。

注意:哪些項目不適合組件化

1、項目較小,模塊間交互簡單,耦合少
2、模塊沒有被多個外部模塊引用,只是一個單獨的小模塊
3、模塊不需要重用,代碼也很少被修改
4、團隊規模很小

二、如何組件化

1、首先進行分層

客戶端APP主流框架都將應用分成三層:
這三層主要有業務層、通用層和基礎層。

  • 業務層。就是我們APP具體的業務。比如常見的首頁、發現、我的等模塊。
  • 通用層。比如常用控件、數據管理、分享、三方登錄等等。
  • 基礎層。比如網絡、數據庫等。

分層的時候需要注意一下幾個原則:

  • 只能上層對下層依賴
    比如業務層可以依賴通用層和基礎層,通用層也可以依賴基礎層,但是通用層和基礎層不能依賴業務層,基礎層也不能依賴通用層。
  • 公共代碼下沉
    當我們遇到同層多個模塊都會引用的代碼時,可以考慮代碼下沉,封裝成一個通用業務模塊,使得代碼可以被復用,提高效率。
  • 減少橫向依賴、適當下沉
    同層間相互依賴可以適當增加中間件(Mediator)進行解耦,這樣中間件下沉,減少同層間直接依賴。

2、模塊劃分

模塊的劃分是一個比較難的點。除了要考慮業務本身,還要考慮到團隊人員情況。要怎么劃分、劃分的粒度有多大要具體情況具體分析。

但是單從技術的角度考慮,模塊劃分應該從基礎層開始劃分。為什么呢?

  • 首先,最上層通常是業務層,如果先從上層開始劃分會直接影響到具體業務,而且組件劃分不是一朝一夕能完成,中間肯定會影響業務的開發;
  • 其次,按照大家的正常思維,上層依賴下層,上層的代碼依賴的代碼比較多,如果從上層開始,就會出現減代碼的情況,會存在很多不可預估的風險;
  • 再次,從基礎層開始的話,因為基礎層不涉及到具體業務,也不影響業務開發。基礎層可以再開發、測試完成確定穩定可靠之后再讓上層代碼慢慢接入,這時候上層代碼在劃分的時候遇到對基礎的依賴就可以直接替換成新的基礎模塊,平滑進行過度。

對于通用業務層的劃分,如果是比較確定的可以提前劃分好。有些模塊可能需要在業務層具體劃分的時候視情況增加。業務層的劃分就得具體情況具體分析了。模塊劃分時遇到交叉依賴問題,適當增加中間件解耦(Mediator),常用的解耦方式有:路由、target-action (CDMediator, invocation)和protocol classes。具體可以參考下面的常見組件化方案的比較。

常見組件化方案的比較

可以直接參考網友的一篇文章iOS組件化方案對比;

通過Cocoa Pod來管理組件

組件拆分之后通過Pod來管理,Pod創建私有庫參考使用Cocoapods開發SDK;

通過代理方式實現組件化方案原理

這里的組件化方案只針對業務層和主app之間以及業務層和業務層之間組件化的方案。因為對于基礎成和通用業務層,他們都屬于下層代碼,功能相對單一,層間依賴較少甚至,比較容易劃分,而且相對比較穩定,業務層是可以通過各自的中間層或者直接依賴的。

代理方案實現組件化的三個要素

核心實現由三大部分組成:組件管理模塊、協議模塊和和各個業務組件模塊。主APP和組件以及組件間的通信通過代理來實現。首先組件管理模塊負責組件和協議的映射管理;協議模塊聲明了各個模塊提供給外部訪問的代理方法;各個組件有對應的代理實現了對應協議。每個組件都要依賴組件管理模塊和協議模塊,動態將各自模塊的協議和負責實現協議代理對象的類名注冊到組件管理模塊的映射表中,同時可以根據需要可以通過協議模塊中其他組件的代理方法與其他組件進行通信。

各組件的協議和實現協議的代理對象是任意的嗎?

為了便于管理,每個組件對外的協議都有一個專門的中介層來實現這個協議,這個中介層負責組件內部和組件外部通信。這個中介層通常是一個對象,當可能存在多個協議時,可以通過分類等方式進行模塊化。這樣做的目的是為了減少組件間通信的復雜度,保持內部對外通信的統一性。外部訪問這個組件時只需要找到這個組件對應的一個協議就可以了,而內部只需要有一個專門的中介層來接收外部信息,并統一在組件內部進行調度,使得整個通信流程有法可依、有跡可循。

組件的協議和其實現類是在什么時機注冊到映射表中的呢?

  • 第一種方法可以在代理類的+load方法中動態注冊,直接注冊代理對象和協議的映射關系,但是這樣做的代碼很多的話會影響啟動速度。至于為什么會影響啟動速度,這個問題涉及到類的加載流程,具體參考OC類的加載流程;
@implementation LNFeedModule
+(void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [[LNModuleManager sharedInstance] addImpClassName:@"LNFeedModule" protocolName:@"LNFeedModuleProtocol"];
    });
}

實現+load方法會導致LNFeedModule稱為非懶加載類,在程序啟動是加載,消耗性能。

  • 第二種方法是在C++構造方法中注冊, 這樣做雖然也會導致LNModuleManager被提前加載,但是LNModuleManager只會被加載一次,影響不大。而且本社注冊類名和協議名稱只不過是簡單的字典操作,耗時較少,對啟動性能影響不大,最重要的是實現簡單。每個模塊實現自己的C++構造方法即可。
//注冊類名和協議名稱
__attribute__((constructor)) void addModulLNFeedModule(void){
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [[LNModuleManager sharedInstance] addImpClassName:@"LNFeedModule" protocolName:@"LNFeedModuleProtocol"];
    });
}
  • 第三種方法可以參考阿里的BeeHive組件化工具。利用注解在程序編譯時把對應的協議的實現和協議寫入mach-o文件中,待鏡像文件加載時讀取,性能較好。實現雖然復雜,但是也已經封裝好了,可以直接用。

每個組件如何監聽APP的生命周期?

每個組件對應的協議都會繼承UIAppDelegate協議,這樣每個組件都可以根據需要監聽應用程序的代理方法,完成自身相關的操作。

總結

架構設計的最終目的是為了提高效率。拋開開發人員自身的問題,這個效率主要有幾個方面影響:其一、程序架構是否簡單易懂,越簡單易懂,自然越容易保障開發效率;其二、盡可能少寫代碼,代碼復用率高,復用率高一定程度上能提高開發效率;其三、專注于自身業務,盡量的少關注其他人。組件化不要只是為了解耦而解耦,而是一切以是否有利于提高效率為目的。解耦應該是為了可擴展性、可維護性和可復用性等,但同時要兼顧可讀性和可靠性。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容