一. 總述
這個方案的最終目的很明確,就是要抽象出一箇中間層來對紛亂的引用關係進行統一的跳轉。模組只和中間層耦合,模組間解耦;中間層使用runtime的形式呼叫模組的業務元件,不依賴具體的模組程式碼.
二. 業務場景
脫離業務需求的設計都是空中樓閣,下面將結合具體的業務場景進行方案分析和設計。
下面是大多數app都會遇到的業務場景:
- 啟動開屏廣告是個運營位置,需要任意跳轉
- 輪播圖是個運營位置,需要任意跳轉
- webview中通過bridge,也存在任意跳轉原生的可能性
- 模組間各種push
簡單粗暴的方法,就是switch判斷型別,引入各種標頭檔案,直接跳轉。隨著業務膨脹,以下弊端會越來越明顯:
- 沒有統一的跳轉管理,每塊業務都維護了自身的一套邏輯,使得跳轉邏輯四處散落;
- 如果要新增一種跳轉型別,如1所述,必須要找到四處的跳轉邏輯,把可能的地方都加一遍,維護起來非常費勁;
- 模組間檔案直接引用,耦合嚴重。如果修改刪除了某個模組間的某個檔案,會直接影響到所有引用了他的檔案;理想期望是獨立模組可以編譯執行,每個模組可隨意插拔;
- 模組間的耦合導致模組無法複用;理想期望是一個模組複製到新專案時,可以直接編譯執行;
三.梳理分析
通過上述業務場景,最直觀的痛點的跳轉管理問題,但本質上都是耦合的問題,是檔案間的引用耦合。 要呼叫一個類的方法,import相關的標頭檔案,再根據標頭檔案暴露的api進行呼叫,這再正常不過了。當業務膨脹後,模組內可以直接耦合引用,但模組間就必須想辦法解耦。 目前業內有兩種主流方案:
- 以JLRoutes為代表的URLRoute方案:以URL為key,以待執行的block為value,儲存在一個全域性map中,在記憶體中常駐;
- Mediator中間人方案:把所有的呼叫都集合在一起,使用一箇中間人管理。所有呼叫方都通過中間人調取另外一個模組;
這兩種方案單獨來說都有各自的優劣勢:
- URLRoute方案借鑑了web的思路,非常契合遠端呼叫這種場景。但是在開發時本地原生呼叫時,使用者會感到變扭。我們更習慣調方法,給方法賦值。在使用URLRoute時,我們還需要多一步轉換邏輯,將方法呼叫轉換成URL形式。這既不方便,同樣存在漏洞。因為URL中只能攜帶常規資料,無法把類似UIImage等格式的資料封裝在URL中。
- Mediator方案相比起來對原生呼叫就非常友好,但當遇見app://user/:userName這樣的跳轉需求時,就顯得不那麼契合。 詳情的分析可以參考https://casatwy.com/iOS-Modulization.html.
四. 結合URLRoute和Mediator的跳轉方案
分析到這裡,其實最後的方案已經呼之欲出了。我們即想要URLRoute這種完美契合schema模型的遠端呼叫體驗,也想要Mediator本地方法呼叫的體驗。
方案如圖所示,具體demo在https://github.com/xuzhenhao/ZHMediator。下面將結合元件化的思路一起闡述整個方案如何落地。
- 模組化拆分。
- 首先要進行模組的劃分,只有模組間的呼叫才會考慮這種方式,模組內的呼叫是允許相互間耦合的,因此合理的模組劃分就很重要。
- 暴露Target物件。Target物件暴露整個模組對外提供的所有服務,此外,因為Mediator和Target是通過Runtime互動的,Target暴露的方法中接收的引數是一個字典,但在方法實現中負責將傳過來的字典還原成各個引數,並呼叫該模組具體的類和方法。
- 編寫模組的Mediator分類。如上所述,受限於runtime只能以字典形式傳一系列引數,Mediator分類的職責就在於對外提供引數友好型的一系列方法,但在方法實現中包裝成字典形式。這裡涉及到key的定義必須和Target中還原時的key定義一致,因此劃分給相同的開發維護。
- 遠端呼叫
- 配置URL解析規則。類似app://user/:userId/detail 這樣的URL,需要通過解析規則,解析出TargetName、ActionName、Params,交付給Mediator進行處理。目前只是簡單基於JLRoutes的二次封裝,當然這裡可以根據需求,後續也能方便替換。這部分配置建議統一放在AppDelegete+Routes的分類中統一配置。
至此,核心部分就介紹完畢了。完整的demo可以在https://github.com/xuzhenhao/ZHMediator獲得。