iOS組件化思路-大神博客研讀和思考

1、大神博客研讀

隨着應用需求逐步迭代,應用的代碼體積將會愈來愈大,爲了更好的管理應用工程,咱們開始藉助CocoaPods版本管理工具對原有應用工程進行拆分。可是僅僅完成代碼拆分還不足以解決業務之間的代碼耦合,爲了更好的讓拆分出去的業務工程可以獨立運行,必須進行組件拆分而且實現組件服務化。css

下面是最近在行業內幾個大神的博客辯論對戰,具體資料以下:html

最近在參考大神們的討論和以前的LDBusBundle方案基礎上上,提煉出了一個適合中小型應用的LDBusMediator中間件,正逐漸在項目中使用。java

博客介紹:http://www.jianshu.com/p/196f66d31543
中間件Git開源地址:https://github.com/Lede-Inc/LDBusMediator.gitandroid

(1)蘑菇街的組件化方案

文章來源:

2016.03.10 蘑菇街App的組件化之路: http://limboy.me/ios/2016/03/10/mgj-components.htmlios

爲何要組件化?
  • 組件和組件之間沒有明確的約束;
  • 組件單獨開發、單獨測試,不能揉入主項目中開發,測試也能夠針對性的測試;
如何管理短鏈?(url跳轉)
[MGJRouter registerURLPattern:@"mgj://detail?id=:id" toHandler:^(NSDictionary *routerParameters) { NSNumber *id = routerParameters[@"id"]; // create view controller with id // push view controller }]; [MGJRouter openURL:@"mgj://detail?id=404」]

短鏈如何管理?git

  1. 後臺專門管理短鏈;平臺生成所需的文件,ios平臺生成h,m文件,android生成java文件,注入到項目中;
  2. 開發人員查看生成文件瞭解全部可用URL;
  3. 缺點:沒法把參數傳遞也經過生成方式得到;
同步的Action調用?(服務調用)

方法一:經過url的方式github

[MGJRouter registerURLPattern:@"mgj://cart/ordercount" toObjectHandler:^id(NSDictionary *routerParamters){ // do some calculation return @42; }] NSNumber *orderCount = [MGJRouter objectForURL:@"mgj://cart/ordercount」]

方法二:經過protocol-class對應的方式sql

把公共協議文件統一放到PublicProtocolDomain.h中,全部業務組件只依賴這個文件;protocol只能經過類方法提供?安全

@protocol MGJCart <NSObject> + (NSInteger)orderCount; @end [ModuleManager registerClass:MGJCartImpl forProtocol:@protocol(MGJCart)] [ModuleManager classForProtocol:@protocol(MGJCart)]
組件生命週期的管理:(組件管理)

啓動初始化時,實例APP中全部組件的module實例,讓每一個組件的module實例執行一遍didFinishLaunchingWithOptions方法:在這方法中每一個組件註冊本身的URL,使用class註冊;每一個組件能夠自行監控系統的通知,如UIApplicationDidBecomeActiveNotification, 對於沒有系統通知消息則將此方法寫入module的protocol中,依次執行實例的這些protocol方法;ruby

[[ModuleManager sharedInstance] loadModuleFromPlist:[[NSBundle mainBundle] pathForResource:@"modules" ofType:@"plist"]]; NSArray *modules = [[ModuleManager sharedInstance] allModules]; for (id<ModuleProtocol> module in modules) { if ([module respondsToSelector:_cmd]) { [module application:application didFinishLaunchingWithOptions:launchOptions]; } }
組件化版本管理的問題
  1. 版本同步問題: API接口改動升級(舊接口不存在了,不向下兼容),版本的中位號發生改變;須要全部依賴其的調用都發生改變,才能保證殼工程和主工程可以同步編譯經過;
  2. pod update以後編譯太長: 考慮經過framework的方式進行修改;
  3. 持續集成問題: 不能只是把podspec直接扔到private repo裏完事,須要扔到主工程進行打包編譯,編譯經過容許提供版本升級,不經過扔回去進行處理;CI編譯檢查,經過以後再將版本號升級到private repo中,同時修改主工程中Podfile的版本依賴號; 但若是是其它工程呢,被多個業務工程所依賴,如何辦?
蘑菇街開源組件:

MGJRouter: https://github.com/mogujie/MGJRouter.git

  1. JLRoutes 的問題主要在於查找 URL 的實現不夠高效,經過遍歷而不是匹配。還有就是功能偏多。
  2. HHRouter 的 URL 查找是基於匹配,因此會更高效,MGJRouter 也是採用的這種方法,但它跟 ViewController 綁定地過於緊密,必定程度上下降了靈活性。

(2)反革命的組件化方案

文章來源:
蘑菇街的方案爲何很差?
  • url註冊對於實施組件化是徹底沒有必要的,拓展性和可維護性都下降;
  • 基於openURL的方案的話,有一個致命缺陷:很是規對象沒法參與本地組件間調度;可是能夠經過傳遞params來解決,可是這樣區分了遠程調用和本地調用的入口;
  • 模塊內部是否仍然須要使用URL去完成調度?是沒有必要的,爲啥要複雜化?
反革命的組件化方案:

基於Mediator模式和Target-Action模式:

[CTMediator sharedInstance]  
openUrl:url] //call from other app with url parseUrl performTarget:action:params //call form Native Module runtime [TargetA action1], [TargetA action2] [TargetB action1], [TargetB action2]
反革命組件化方案的調用方式:

本地跨組件間調用:

[[CTMediator sharedInstance] performTarget:targetName action:actionName params:@{…}]

遠程應用調用:

openUrl + parseUrl的方式; 針對請求的路由操做,直接將Target和Action的名字封裝到url中;
反革命組件化方案的好處:
  1. 將遠程調用和本地調用作了拆分,並且由本地應用調用位遠程應用調用提供服務;
  2. 組件僅經過Action暴露可調用接口;
  3. 組件化方案必需去Model設計:只有調用方依賴Mediator,響應方依賴是沒有必要的;
  4. 調用方如何知道接收方須要哪些Key的參數,如何知道有哪些target可被調用?:在mediator中維護針對Mediator的Category,每一個category對應一個target,categroy中的方法對應Action場景;
    • category爲組合模式,根據不一樣的分類提供不一樣的方法,每一個組件對應一個category分類;
    • 參數驗證和補救入口;
    • 輕鬆的請求轉發;
    • 統一了全部組件間調用入口;
    • param的hardcode在整個app的做用域僅僅存在於category中,跟調用宏差很少;
    • 安全保證,對url中進行native前綴驗證;
    • 保證動態調度考慮;
反革命組件化方案開源Demo:

代碼Git地址:https://github.com/casatwy/CTMediator.git

2、實際項目中的組件化問題

(1) 爲何要組件化?

  • 解決人多(更好的協做)、需求多(更好的功能模塊劃分)的問題;
  • 解決項目模塊間的代碼耦合問題;(堅定抵制業務組件間代碼直接引用)

(2)如何拆分組件?(神仙們討論的主要是產品業務組件化的問題)

  • 基礎功能組件:(相似於性能統計、Networking、Patch、網絡診斷等)

    • 按功能分庫,不涉及產品業務需求,跟庫Library相似
    • 經過良好的接口拱上層業務組件調用;
    • 不寫入產品定製邏輯,經過擴展接口完成定製;
  • 基礎UI組件:(例以下拉刷新組件、iCausel相似的組件)

    • 產品內通用UI組件;(各個業務模塊依賴使用,但須要保持好定製擴展的設計)
    • 公共通用UI組件;(不涉及具體產品的視覺設計, 目前較少)
  • 產品業務組件:(例如圈子、1元購、登陸、客服MM等)

    • 業務功能間相對獨立,相互間沒有Model共享的依賴;
    • 業務之間的頁面調用只能經過UIBus進行跳轉;
    • 業務之間的邏輯Action調用只能經過服務提供;

(3)組件化工程須要解決的問題?

  • 組件化頁面跳轉(UIBus)方案要求:

    • 可以傳遞普通參數(系統基礎數據類型)和複雜參數(url沒法負載的對象),不負責CustomModel的傳遞處理;
    • 可以獲取url對應的controller進行TabController的動態配置;
    • 可以對controller的present方式進行定製;
    • url的註冊須用代碼完成,必需去中心化處理;
  • 組件服務化(ServiceBus)方案要求:

    • 可以傳遞普通參數和複雜參數,儘可能不使用CustomModel的傳遞;
    • 經過接口文件的統一基礎庫進行依賴;(業務開發方開發階段只須要依賴接口文件依賴庫,在主項目集成測試階段依賴全部業務組件進行測試)
    • 接口和實現類的的對應註冊須用代碼完成,由中間件去控制服務實現類的生成;

(通用問題:複雜參數傳遞 key值的硬編碼問題)

(4)組件維護問題?

3、關於組件化的思考和總結

MGJRouter+ModuleManager方案 (蘑菇街方案)
CTMediator+Target-Action方案 (反革命方案)

(1)主要解決本地業務組件之間的通訊問題

組件化主要仍是解決本地業務組件間的調用,至於跨App或者Hybrid頁面經過openUrl方式調用頁面和服務的方式實際上是能夠拆分紅兩個步驟的問題:特定模塊解析處理+中間件調用。跨App經過info.plist配置的scheme跳轉進入,hybrid頁面經過JSBridge框架跳轉進入,這部分都有特定的模塊去解析完成。在特定的模塊中是否要調用其它業務組件的頁面或者服務由特定模塊自行決定,這不是組件化中間件要去完成的事情。

(2)從工程代碼層面來講,組件化就是經過中間件解決組件間頭文件直接引用、依賴混亂的問題;

從實際開發來講,組件之間最大的需求就是頁面跳轉,須要從組件A的pageA1頁面跳轉到組件B的pageB1頁面,避免對組件B頁面ViewController頭文件的直接依賴。其次就是服務的調用,服務調用模塊毫不是爲了解決url跳轉的問題,只是服務調用方式能夠用來解決頁面跳轉的需求,可是沒有url跳轉方案成本低。因此纔有了蘑菇街方案的MGJRouter和ModuleManager的class-protocol方案的區別;而反革命的方案仍然用Target-Action方案來解決頁面跳轉問題,成本稍大;並且url跳轉和服務調用是兩種不一樣的組件間通訊需求,用兩種不一樣的方式來完成更有區分度。

(3)純中間件只負責掛接節點的通訊問題,不該涉及掛接點具體業務的任何邏輯。

中間件若是涉及到具體的業務邏輯,勢必形成中間件對業務模塊的直接依賴,因此中間件只須要抽象出業務通訊的基本職責,規定好協議接口,完成調度功能便可。

而每一個掛接節點(這裏指業務組件)遵循中間件的協議完成掛接工做,固然這會形成掛接節點對中間件的協議依賴;調用方一樣也必須經過掛接點提供的方法將調用操做push到中間件上,而不用管具體的調用過程,這樣也是掛接節點依賴中間件,業務邏輯並無直接依賴中間件。這就是以前阿里無線分享的bus總線的思路,經過這種思路即便切換或者去掉中間件,都只須要在掛接節點中進行修改就能夠完成,避免了對業務邏輯代碼的直接調用修改。

至於去掉中間件,應用仍然可以跑的命題? 若是沒有任何代碼的修改,就至關於把解藕的橋樑給拆除了,再牛逼的框架也不能知足。

  • 反革命框架的調用方直接依賴中間件提供的調用方法,拆除中間件,至少須要修改調用方法。

(4)中間件是否應該解決組件對外披露url調用和服務接口信息?

中間件解決了組件間的通訊解藕問題,勢必會將組件對外提供調用的信息隱藏起來,否則就不能達到解藕通訊的目標。

蘑菇街方案的披露方法:

  • url短鏈在後臺管理,自動生成可查看的.{h,m,java}文件,開發人員經過這個文件進行查看,代碼文件跟文檔功能相似;沒法解決參數key、類型的問題;
  • 服務調用接口統一放到PublicProtocol.h文件上,其它全部業務組件均須要依賴這個文件;

(是否把url短鏈和publicProtocol文件統一放到一個repo裏,其實就至關於說明文檔的做用)

反革命方案的披露方法:

  • 經過依賴中間件的category(target)方式,將業務組件的全部調用都經過category的方法暴露出來;
  • 優勢:解決了url參數key、類型檢查的問題;經過Target-Action方式同時解決了url短鏈和服務調用的通訊需求,並且更加適合程序猿的風格來解決問題。

4、咱們的組件化方案

以前聽阿里的組件化分享以後,本身作了一套有關Bus總線的方案,可是在具體的產品使用過程當中用起來仍是麻煩,在項目中推廣起來難度仍是比較大。特別是關於組件對外披露信息的部分,到如今都沒有一個好的思路,雖然反革命的方案解決了披露的問題,可是我以爲擴展性和可維護性上仍是比較差。

git開源地址:https://github.com/Lede-Inc/LDBusBundle_IOS.git

最近研讀幾個大神的博客和討論以後,有了一些新的思路,但願可以繼續按照bus+category的思路上去專研一下,但願可以一個真正適合在項目裏推行起來的方案。

最近在參考大神們的討論和以前的LDBusBundle方案基礎上上,提煉出了一個適合中小型應用的LDBusMediator中間件,正逐漸在項目中使用。

博客介紹:http://www.jianshu.com/p/196f66d31543
中間件Git開源地址:https://github.com/Lede-Inc/LDBusMediator.git



文/philon(簡書做者) 原文連接:http://www.jianshu.com/p/afb9b52143d4 著做權歸做者全部,轉載請聯繫做者得到受權,並標註「簡書做者」。
相關文章
相關標籤/搜索