iOS組件化方案選型

1、組件化概念

1.一、項目目前現狀

  • 各模塊直接調用,耦合嚴重。業務模塊間劃分不清晰,相互引用,模塊之間耦合度很大,很是難維護。
  • 全部模塊代碼都編寫在一個項目中,測試某個模塊或功能,須要編譯運行整個項目,不能獨立運行。

耦合嚴重

1.二、解決方案

  • 全部的模塊間的調用都會通過中間層中轉(參考Router),可是發現增長這個中間層後,耦合仍是存在的。
  • 中間層對被調用模塊存在耦合,其餘模塊也須要耦合中間層才能發起調用。這樣仍是存在以前的相互耦合的問題,雖然可解決了統一調用的問題,並且本質上比以前更麻煩了。

錯誤解耦

1.三、正確的組件化解耦

  • 正確的解耦應該是,只讓其餘模塊對中間層產生耦合關係,中間層不對其餘模塊發生耦合。
  • 對於這個問題,能夠採用組件化的架構,將每一個模塊做爲一個組件。而且創建一個主項目,這個主項目負責集成全部組件。

正確解耦

2、組件化主流方案

2.一、url-block (表明:蘑菇街組件化方案MGJRouter)

蘑菇街經過MGJRouter實現中間層,經過MGJRouter進行組件間的消息轉發,從名字上來講更像是路由器。實現方式大體是,在提供服務的組件中提早註冊block,而後在調用方組件中經過URL調用block,下面是調用方式。html

  • 架構設計

  • MGJRouter組件化架構

架構設計

  • MGJRouter是一個單例對象,在其內部維護着一個「URL -> block」格式的註冊表,經過這個註冊表來保存服務方註冊的block,以及使調用方能夠經過URL映射出block,並經過MGJRouter對服務方發起調用。
  • 在程序開始運行時,須要將全部服務方的接口類實例化,以完成這個註冊工做,使MGJRouter中全部服務方的block能夠正常提供服務。在這個服務註冊完成後,就能夠被調用方調起並提供服務。

2.1.1 、MGJRouter調用,代碼模擬對詳情頁的註冊、調用,在調用過程當中傳遞id參數。下面是註冊的示例代碼。git

[MGJRouter registerURLPattern:@"mgj://detail?id=id" toHandler:^(NSDictionary *routerParameters) {
    // 下面能夠在拿到參數後,爲其餘組件提供對應的服務
    NSString uid = routerParameters[@"id"];
}];
複製代碼

2.1.二、經過openURL:方法傳入的URL參數,對詳情頁已經註冊的block方法發起調用。調用方式相似於GET請求,URL地址後面拼接參數。github

[MGJRouter openURL:@"mgj://detail?id=404"];
複製代碼

2.1.三、也能夠經過字典方式傳參,MGJRouter提供了帶有字典參數的方法,這樣就能夠傳遞非字符串以外的其餘類型參數。web

[MGJRouter openURL:@"mgj://detail?" withParam:@{@"id" : @"404"}];
複製代碼
  • 短鏈管理這時候會發現一個問題,在蘑菇街組件化架構中,存在了不少硬編碼的URL和參數。
  • 在代碼實現過程當中URL編寫出錯會致使調用失敗,並且參數是一個字典類型,調用方不知道服務方須要哪些參數,這些都是個問題。
  • 對於這些數據的管理,蘑菇街開發了一個web頁面,這個web頁面統一來管理全部的URL和參數,Android和iOS都使用這一套URL,能夠保持統一性。

2.二、Protocol方案(表明:阿里的BeeHive)

  • 面向接口調用,咱們知道只要直接引用代碼,就會有依賴,好比:
// A 模塊
- (void)getSomeDataFromB {
   B.getSomeData();
}
// B 模塊
- (void)getSomeData {
   return self.data;
}
複製代碼
  • 那麼咱們能夠實現一個 getSomeDataFromB 的接口,讓 A 只依賴這個接口,而 B 來實現這個接口,這樣就實現了 A 與 B 的解耦。
// 接口
@protocol BService <NSObject>
- (void)getSomeData;
@end
// A 模塊, 只依賴接口
- (void)getSomeDataFromB {
   id b = findService(@protocol(BService));
   b.getSomeData;
}
// B 模塊,實現BService接口
@interface B : NSObject <BService>
- (void)getSomeData {
   return self.data;
}
@end
複製代碼

這樣就能夠實現了即知足了模塊之間調用,也實現瞭解耦。網絡

優勢:架構

  • 接口相似代碼,能夠很是靈活的定義函數和回調等。
  • 解決了硬編碼的問題。
  • 缺點:
  • 接口定義文件須要放在一個模塊以供依賴,可是這個模塊不回貢獻代碼,因此還好。
  • 使用較爲麻煩,每各調用都須要定義一個service,並實現, 對於一些具備普適性規律的場景不太合適,好比頁面統一跳轉。

BeeHive框架框架

  • Module負責管理模塊的註冊和釋放
  • Protocol負責公開組件開放的接口

BeeHive框架

優點:擴展組件的生命週期,大廠開源 注意:BeeHive採用GPL開源協議,如有修改,不容許私有化,必須開源分享。模塊化

2.三、Target-Action方案(casatwy組件化方案)

2.3.一、調用方式

  • 總體架構casatwy組件化方案分爲兩種調用方式,遠程調用和本地調用,對於兩個不一樣的調用方式分別對應兩個接口。函數

  • 遠程調用經過AppDelegate代理方法傳遞到當前應用後,調用遠程接口並在內部作一些處理,處理完成後會在遠程接口內部調用本地接口,以實現本地調用爲遠程調用服務。工具

  • 本地調用由performTarget:action:params:方法負責,但調用方通常不直接調用performTarget:方法。CTMediator會對外提供明確參數和方法名的方法,在方法內部調用performTarget:方法和參數的轉換。

2.3.二、架構設計思路

  • casatwy是經過CTMediator類實現組件化的,在此類中對外提供明確參數類型的接口,接口內部經過performTarget方法調用服務方組件的Target、Action。
  • 因爲CTMediator類的調用是經過runtime主動發現服務的,因此服務方對此類是徹底解耦的。
  • 但若是CTMediator類對外提供的方法都放在此類中,將會對CTMediator形成極大的負擔和代碼量。
  • 解決方法就是對每一個服務方組件建立一個CTMediator的Category,並將對服務方的performTarget調用放在對應的Category中,這些Category都屬於CTMediator中間件,從而實現了感官上的接口分離。

2.3.三、casatwy組件化實現細節

  • 對於服務方的組件來講,每一個組件都提供一個或多個Target類,在Target類中聲明Action方法。
  • Target類是當前組件對外提供的一個「服務類」,Target將當前組件中全部的服務都定義在裏面,CTMediator經過runtime主動發現服務。
  • 在Target中的全部Action方法,都只有一個字典參數,因此能夠傳遞的參數很靈活,這也是casatwy提出的去Model化的概念。
  • 在Action的方法實現中,對傳進來的字典參數進行解析,再調用組件內部的類和方法。

3、組件化方案對比

3.一、url-block方式

  • 硬編碼問題,每一個組件參數調用都須要查找對應。蘑菇街爲此開發了一個web頁面,這個web頁面統一來管理全部的URL和參數。
  • 須要在內存中維護url-block的表,組件多了可能會有內存問題。
  • url的參數傳遞受到限制,只能傳遞常規的字符串參數,沒法傳遞很是規參數,如UIImage、NSData等類型。
  • 沒有區分本地調用和遠程調用的狀況,尤爲是遠程調用,會由於url參數受限,致使一些功能受限。
  • 組件自己依賴了中間件,且分散註冊使的耦合較多。

3.二、Protocol方案

  • 0硬編碼,代碼可讀性高;
  • Protocol方案須要在啓動的時候向ProtocolManager註冊,侵入較大。

3.三、Target_Action方案

  • 侵入最小,但硬編碼較多。
  • runtime編譯階段不檢查,運行時才檢查對應類或者方法是否存在,對開發要求較高。

4、組件化實現原則

4.一、抽象化原則

  • 越底層的模塊,應該越穩定,越抽象,越具備高複用度。
  • 穩定的最直觀表現就是API好久都不用變化,全部的變化因子不要暴露出來,避免傳遞給依賴它的模塊。
  • 可是要作到設計一套API好久都不用改變,那麼就須要設計的時候能越抽象, 即須要咱們抽象總結的能力。

4.二、穩定性原則

不要讓穩定的模塊依賴不穩定的模塊, 減小依賴,穩定性 還有一個特色就是會傳遞,好比 B 模塊依賴了 A 模塊,若是 B 模塊很穩定,可是 A 模塊不穩定,那麼B模塊也會變的不穩定了。

4.三、自備性完整

提高模塊的複用度,自完備性有時候要優於代碼複用;什麼是自完備性,就是儘量的依賴少的模塊來達到代碼可複用;我有個模塊 Utils 裏面放了大量的category工具方法等,在平常UI產品開發中,依賴這個Utils會很方便,可是我如今要寫一個比較基礎的模塊,應該就要求複用度更高一些,這個時候須要用到Utils裏面的幾個方法,那這個時候還適合直接依賴Utils嗎,固然不合適了,這與咱們上面的設計原則相悖了啊,所以咱們這時候爲了這個模塊的自完備性,就能夠從新實現下這幾個方法,而不是依賴Utils模塊。

4.四、不要讓Common出現

每一個模塊只作好一件事情,不要讓Common出現,按照你架構的層數從上到下依賴,不要出現下層模塊依賴上層模塊的現象,業務模塊之間也儘可能不要耦合。

4.五、業務模塊真正解耦

爲何要解耦吧,模塊化並非說你把工程的代碼拆分紅 50 個 pod 或者framework就算完事了,要實現模塊之間真正的解耦纔算真正的模塊化,不然若是模塊之間還都是互相調用代碼,循環依賴,那麼和本來放文件夾裏面沒啥兩樣。那麼什麼是模塊間的解耦呢?模塊解耦的目標就是, 在基於模塊設計原則上, 讓模塊之間沒有循環依賴, 讓業務模塊之間解除依賴。

4.六、單向依賴

基礎模塊下沉,這塊其實仍是講的模塊設計,一個工程的架構可能會分爲不少層,然而在開發的過程當中,很容易有人不注意讓應該處於較底層的模塊依賴了上層的模塊,這種狀況下應該對模塊的設計進行改造實現單向依賴。

5、組件化具體實施步驟

5.一、組件化第一步,剝離產品公共庫和基礎庫

包括組件中間件,網絡請求,第三方SDK管理封裝,WebView(封裝js,且以服務形式提供),自定義鍵盤,UI基礎組件,分類。而後在項目裏用pod進行管理。其中,針對三方庫,最好再封裝一層,使咱們的項目不直接依賴三方庫,方便後續開發過程當中的更換。

5.二、組件化第二步,獨立業務模塊單獨成庫

拆分粒度能夠先粗後細,將相對獨立的組件拆分出來。在開發過程當中,對一些獨立的模塊,如:登陸模塊、帳戶模塊等等,也能夠封裝成組件,由於這些組件是項目強依賴的,調用的頻次比較多。另外,在拆分組件化的過程當中,拆分的粒度要合適,儘可能作到組件的獨立性。同時,組件化是一個漸進的過程,不可能把一個完整的工程一會兒所有組件化,要分步進行,經過不停的迭代,來最終實現項目的組件化。

5.三、組件化第三步,對外服務最小化

在前兩步都完成的狀況下,咱們能夠根據組件被調用的需求來抽象出組件對外的最小化接口。

6、總結

組件化方案選型到此結束,但實際實施起來估計又會遇到各類蛋疼的事,尤爲是一些舊項目,一堆一堆無註釋的舊代碼舊邏輯,看起來沒用,但又不敢貿然刪除,每刪除一段代碼就須要把像粑粑同樣的代碼邏輯捋一遍,箇中滋味不便於人細說!

若是您以爲有所幫助,請在GitHub上賞個Star ⭐️,您的鼓勵是我前進的動力

7、參考

  • http://limboy.me/tech/2016/03/10/mgj-components.html
  • http://limboy.me/tech/2016/03/10/mgj-components.html
  • http://www.open-open.com/lib/view/open1487318191631.html
  • http://blog.cnbang.net/tech/3080/
  • http://limboy.me/tech/2016/03/10/mgj-components.html
相關文章
相關標籤/搜索