上篇我們知道了如何創(chuàng)建組件化項(xiàng)目,這篇我們來聊聊組件化的重點(diǎn):組件化通信
組件化通信方法
目前所了解的主流方式有一下三種:
- 1.
URL
路由 - 2.
target-action
- 3.
protocol
匹配
協(xié)議試編程
在編譯層面使用協(xié)議定義規(guī)范
,實(shí)現(xiàn)在不同地方
距淫,從而達(dá)到分布管理
和維護(hù)組件的目的
。這種方式也遵循了依賴反轉(zhuǎn)
原則颁股,是一種很好的面向?qū)ο缶幊?/code>的實(shí)踐惜索。
但是方案也很明顯:
- 由于
協(xié)議式編程缺少統(tǒng)一調(diào)度層
,導(dǎo)致難于集中管理
则剃,特別是項(xiàng)目規(guī)模變大
、團(tuán)隊(duì)變多
的情況下如捅,架構(gòu)管控就會(huì)顯得越來越重要
棍现。 -
協(xié)議式編程接口
定義模式過于規(guī)范
,從而使得架構(gòu)的靈活性不夠高
镜遣。當(dāng)需要引入一個(gè)新的設(shè)計(jì)模式來開發(fā)
時(shí)己肮,我們就會(huì)發(fā)現(xiàn)很難融入到當(dāng)前架構(gòu)
中,缺乏架構(gòu)的統(tǒng)一性
悲关。
中間者
它采用中間者統(tǒng)一管理
的方式谎僻,來控制App的整個(gè)生命周期中組件間的調(diào)用關(guān)系
。同時(shí)iOS對(duì)于組件接口
的設(shè)計(jì)也需要保持一致性
寓辱,方便中間者統(tǒng)一調(diào)用
艘绍。
拆分的組件
都會(huì)依賴
于中間者
,但是組間之間
就不存在
相互依賴的關(guān)系了
秫筏。由于其他組件
都會(huì)依賴
于這個(gè)中間者
鞍盗,相互間的通信
都會(huì)通過中間者統(tǒng)一調(diào)度
,所以組件間的通信
也就更容易管理
了跳昼。在中間者
上也能夠輕松添加新的設(shè)計(jì)模式
般甲,從而使得架構(gòu)更容易擴(kuò)展
。
好的架構(gòu)一定是健壯的鹅颊、靈活的
敷存。中間者架構(gòu)
的易管控帶來的架構(gòu)更穩(wěn)固
,易擴(kuò)展帶來的靈活性
堪伍。
URL路由
這也是很多iOS項(xiàng)目
使用的通信方案
锚烦,它就是基于路由匹配
,或者根據(jù)命名約定
帝雇,用runtime
方法進(jìn)行動(dòng)態(tài)調(diào)用
涮俄,URL路由思路
采用了中間者模式
- 優(yōu)點(diǎn):
實(shí)現(xiàn)簡單
- 缺點(diǎn):需要
維護(hù)字符串表
,或者依賴于命名約定
尸闸,無法
在編譯時(shí)暴露
出所有問題
彻亲,需要在運(yùn)行時(shí)
才能發(fā)現(xiàn)錯(cuò)誤
孕锄。
URL路由的優(yōu)缺點(diǎn)
【優(yōu)點(diǎn)】
-
極高的動(dòng)態(tài)性
,適合經(jīng)常展開運(yùn)營活動(dòng)的app。例如:電商類 -
方便統(tǒng)一管理
多平臺(tái)的路由規(guī)則 易于適配URL Scheme
【缺點(diǎn)】
-
傳參方式有限
,并且無法利用編譯期進(jìn)行參數(shù)類型檢查
(所有的參數(shù)都是通過字符串轉(zhuǎn)換而來) -
只適用于界面模塊
溢陪,不適用于通用模塊
-
參數(shù)格式不明確
,是個(gè)靈活的dictionary轴脐,還需要有個(gè)地方查看參數(shù)格式 不支持storyboard
-
依賴于字符串硬編碼
,難以管理抡砂,蘑菇街為此專門做了一個(gè)后臺(tái)管理這部分 無法保證所有使用的模塊一定存在
-
解耦能力有限
大咱,URL的"注冊","實(shí)現(xiàn)"注益,"使用"必須使用相同的字符串規(guī)則
碴巾,一旦任何一方做出修改都會(huì)導(dǎo)致其他地方的代碼失效,并且重構(gòu)難度大
URL路由方式主要是以蘑菇街為代表的MGJRouter
作為一個(gè)開發(fā)者聊浅,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要餐抢,這是一個(gè)我的iOS開發(fā)交流群:130 595 548现使,不管你是小白還是大牛都?xì)g迎入駐 低匙,讓我們一起進(jìn)步,共同發(fā)展L夹狻(群內(nèi)會(huì)免費(fèi)提供一些群主收藏的免費(fèi)學(xué)習(xí)書籍資料以及整理好的幾百道面試題和答案文檔M缫薄)
MGJRouter
其實(shí)現(xiàn)思路是:
-
App啟動(dòng)時(shí)
實(shí)例化各組件模塊,然后這些組件向MGJRouter注冊URL
售碳,有時(shí)候不需要實(shí)例化
强重,使用Class注冊
- 當(dāng)
組件A
需要調(diào)用
組件B時(shí)
,向ModuleManager傳遞URL
贸人,參數(shù)跟隨URL以GET方式傳遞
间景,類似openURL
。然后由ModuleManager負(fù)責(zé)調(diào)度組件B
艺智,最后完成任務(wù)倘要。
MGJRouter采用URL路由
,但是他的解耦能力
還是有限
除了上面的MGJRouter十拣,還有以下三方框架
target-action
這個(gè)方案是基于OC的runtime封拧、category特性動(dòng)態(tài)獲取模塊
,例如通過NSClassFromString獲取類并創(chuàng)建實(shí)例
夭问,通過performSelector+NSInvocation動(dòng)態(tài)調(diào)用方法
這種方式主要是以casatwy的CTMediator為代表
CTMediator
CTMediator其實(shí)現(xiàn)思路:
- 1.
利用分類
為路由添加新的接口
泽西,在接口
通過字符串獲取對(duì)應(yīng)的類
- 2.通過
runtime
創(chuàng)建實(shí)例,動(dòng)態(tài)調(diào)用實(shí)例的方法
【優(yōu)點(diǎn)】:
- 利用
分類
可以聲明接口
缰趋,進(jìn)行編譯檢查
- 實(shí)現(xiàn)方式
輕量級(jí)
【缺點(diǎn)】:
- 需要在
mediator和target
中重新添加
每一個(gè)接口
捧杉,模塊化
時(shí)代碼
較為繁瑣
- 在
category
中仍然要引入字符串硬編碼
陕见,內(nèi)部使用字典傳參
,一定程度上也存在和URL路由相同的問題
-
無法保證使用的模塊一定存在
糠溜,target在修改后
淳玩,使用者只能在運(yùn)行時(shí)才能發(fā)現(xiàn)錯(cuò)誤
- 創(chuàng)建
過多的target類
,導(dǎo)致target類泛濫
CTMediator源碼分析
CTMediator使用URL路由處理這個(gè)方法主要是針對(duì)遠(yuǎn)程APP的互相調(diào)起,通過openURL實(shí)現(xiàn)APP之間的跳轉(zhuǎn),通過URL進(jìn)行數(shù)據(jù)傳遞
CTMediator使用的是運(yùn)行時(shí)解耦
非竿,解耦核心方法如下所示:
-
performTarget:action:params:shouldCacheTarget:
方法主要是對(duì)targetName
和actionName
進(jìn)行容錯(cuò)處理蜕着,也就是對(duì)調(diào)用方法無響應(yīng)的處理。 - 這個(gè)方法封裝了
safePerformAction:target:params
方法红柱,入?yún)?code>targetName就是調(diào)用接口的對(duì)象承匣,actionName是調(diào)用的方法名
,params是參數(shù)
锤悄。 - 并且代碼中同時(shí)還能看出只有
滿足Target_ 前綴的類的對(duì)象
和Action_的方法才能被CTMediator使用
韧骗。這時(shí),我們可以看出中間者架構(gòu)的優(yōu)勢零聚,也就是利于統(tǒng)一管理袍暴,可以輕松管控制定的規(guī)則。
下面主要看下有action
的情況
protocol class
protocol匹配的實(shí)現(xiàn)思路
是:
- 1.將
protocol
和對(duì)應(yīng)的類
進(jìn)行字典匹配
- 2.通過用
protocol獲取class
隶症,再動(dòng)態(tài)創(chuàng)建實(shí)例
protocol
比較典型的三方框架就是阿里的BeeHive政模。BeeHive
借鑒了Spring Service、Apache DSO
的架構(gòu)理念蚂会,采用AOP+擴(kuò)展App生命周期API
形式淋样,將業(yè)務(wù)功能
、基礎(chǔ)功能
模塊方式以解決大型應(yīng)用
中的復(fù)雜問題胁住,并讓模塊之間以Service形式調(diào)用
趁猴,將復(fù)雜問題切分
,以AOP方式模塊化服務(wù)
BeeHive核心思想
- 1.
各個(gè)模塊間調(diào)用
從直接調(diào)用對(duì)應(yīng)模塊
彪见,變成調(diào)用Service的形式
儡司,避免直接依賴
- 2.App生命周期的分發(fā),將
耦合在AppDelegate中邏輯拆分
余指,每個(gè)模塊以微應(yīng)用
的形式獨(dú)立存在捕犬。
【優(yōu)點(diǎn)】
- 1.
利用接口調(diào)用
,實(shí)現(xiàn)參數(shù)傳遞時(shí)的類型安全
- 2.直接使用
模塊的protocol接口
浪规,無需再重復(fù)封裝
【缺點(diǎn)】
- 1.用
框架來創(chuàng)建所有對(duì)象
或听,創(chuàng)建方式不同,即不支持外部傳參 - 2.用
OC
的runtime
創(chuàng)建對(duì)象笋婿,不支持Swift
- 3.只做了
protocol
和class
的匹配誉裆,不支持更復(fù)雜的創(chuàng)建方式和依賴注入
- 4.
無法保證所以使用的protocol一定存在對(duì)應(yīng)的模塊
,也無法直接判斷
某個(gè)protocol是否能用于獲取模塊
除了BeeHive還有Swinject
BeeHive中的Module注冊
在BeeHive
主要是通過BHModuleManager
來管理各個(gè)模塊的缸濒。BHModuleManager
中只會(huì)管理已經(jīng)被注冊過的模塊
BeeHive
提供了三種
不同的調(diào)用形式足丢,靜態(tài)plist
粱腻,動(dòng)態(tài)注冊
,annotation
斩跌。Module
绍些、Service
之間沒有關(guān)聯(lián)
,每個(gè)業(yè)務(wù)模塊
可以單獨(dú)實(shí)現(xiàn)Module
或者Service的功能
耀鸦。
Annotation方式注冊
這種方式主要是通過BeeHiveMod宏
進(jìn)行Annotation標(biāo)記
這里針對(duì)__attribute
需要說明一下幾點(diǎn)
- 第一個(gè)參數(shù)
used
:用來修飾函數(shù)
柬批,被used修飾
以后,即使函數(shù)沒有被引用
袖订,在Release
下也不會(huì)被優(yōu)化
氮帐。如果不加這個(gè)修飾
,那么Release環(huán)境鏈接器
下會(huì)去掉沒有被引用的段
洛姑。 - 通過使用
__attribute__((section("name")))
來指明哪個(gè)段
上沐。數(shù)據(jù)則用__attribute__((used))來標(biāo)記
,防止鏈接器
會(huì)優(yōu)化刪除未被使用的段
楞艾,然后將模塊注入
到__DATA中
此時(shí)Module
已經(jīng)被存儲(chǔ)
到Mach-O文件的特殊段
中参咙,那么如何取呢?
-
進(jìn)入BHReadConfiguration方法
硫眯,主要是通過Mach-O
找到存儲(chǔ)
的數(shù)據(jù)段
蕴侧,取出放入數(shù)組
中
讀取本地Pilst文件
- 首先,需要
設(shè)置好路徑
- 創(chuàng)建
plist
文件舟铜,plist
文件的格式也是數(shù)組中包含多個(gè)字典戈盈。字典里面有兩個(gè)key奠衔,一個(gè)是"moduleLevel"
谆刨,另一個(gè)是"moduleClass"
。注意根
的數(shù)組的名字叫"moduleClasses"
归斤。
- 進(jìn)入loadLocalModules方法痊夭,主要是從plist里面取出數(shù)組,然后把數(shù)組加入到BHModuleInfos數(shù)組里
load方法注冊
該方法注冊Module
就是在load方法
里面注冊Module的類
- 進(jìn)入
registerDynamicModule
實(shí)現(xiàn)
其底層還是同第一種方式一樣脏里,最終會(huì)走到addModuleFromObject:shouldTriggerInitEvent:
方法中
- load方法她我,還可以使用
BH_EXPORT_MODULE宏
代替
BH_EXPORT_MODULE宏里面可以傳入一個(gè)參數(shù),代表是否異步加載Module模塊迫横,如果是YES就是異步加載番舆,如果是NO就是同步加載。
BeeHive模塊事件
BeeHive
會(huì)給每個(gè)模塊提供生命周期事件
矾踱,用于與BeeHive宿主環(huán)境
進(jìn)行必要信息交互
恨狈,感知模塊生命周期的變化
。
BeeHive
各個(gè)模塊會(huì)收到一些事件呛讲。在BHModuleManager
中禾怠,所有的事件
被定義成
了BHModuleEventType枚舉
返奉。如下所示,其中有2個(gè)事件很特殊吗氏,一個(gè)是BHMInitEvent
芽偏,一個(gè)是BHMTearDownEvent
主要分三種
- 1.
系統(tǒng)事件
:主要是指Application生命周期事件
通常做法是AppDelegate
改為繼承自BHAppDelegate
- 2.
應(yīng)用事件
:官方給出的流程圖,其中modSetup弦讽、modInit
等污尉,可以用于編碼實(shí)現(xiàn)各插件模塊
的設(shè)置與初始化
。
- 3.自定義事件
以上所有的事件都可以通過調(diào)用BHModuleManager的triggerEvent:
來處理往产。
從上面的代碼中可以發(fā)現(xiàn)十厢,除去BHMInitEvent初始化事件
和BHMTearDownEvent拆除Module事件
這兩個(gè)特殊事件以外,其它所有的事件都是調(diào)用的handleModuleEvent:
方法捂齐,其內(nèi)部實(shí)現(xiàn)主要是遍歷BHModules實(shí)例數(shù)組
蛮放,調(diào)用performSelector:withObject:
方法實(shí)現(xiàn)對(duì)應(yīng)的方法調(diào)用
注意:這里
所有的Module
必須是遵循BHModuleProtocol
的,否則無法接收到這些事件的消息
奠宜。
BeeHive模塊調(diào)用
在BeeHive中是通過BHServiceManager來管理各個(gè)Protocol的包颁。BHServiceManager中只會(huì)管理已經(jīng)被注冊過的Protocol。
注冊Protocol的方式一共有三種压真,和和上面講的注冊Module是一樣一一對(duì)應(yīng)
Annotation方式注冊
讀取本地plist文件
- 首先同Module一樣娩嚼,需要先設(shè)置好路徑
- 設(shè)置plist文件
- 同樣也是在setContext時(shí)注冊services
protocol注冊
主要是調(diào)用BeeHive
里面的createService:
完成protocol
的注冊
createService
會(huì)先檢查Protocol協(xié)議是否是注冊
過的。然后接著取出字典里面對(duì)應(yīng)的Class滴肿,如果實(shí)現(xiàn)了shareInstance
方法岳悟,那么就創(chuàng)建一個(gè)單例對(duì)象
,如果沒有泼差,那么就創(chuàng)建一個(gè)實(shí)例對(duì)象
贵少。如果還實(shí)現(xiàn)了singleton
,就能進(jìn)一步的把implInstance
和serviceStr
對(duì)應(yīng)的加到BHContext
的servicesByName
字典里面緩存
起來堆缘。這樣就可以隨著上下文傳遞了
- 進(jìn)入
serviceImplClass
實(shí)現(xiàn)滔灶,從這里可以看出protocol
和類是通過字典
綁定的,protocol
作為key
吼肥,serviceImp(類的名字)作為value
Module & Protocol
這里總結(jié)一下:
- 對(duì)于
Module
:數(shù)組存儲(chǔ)
- 對(duì)于
Protocol
:通過字典
將protocol
與類
進(jìn)行綁定
录平,key
為protocol
,value
為serviceImp
即類名
輔助類
-
BHConfig
類:是一個(gè)單例
缀皱,其內(nèi)部有一個(gè)NSMutableDictionary
類型的config屬性
斗这,該屬性維護(hù)了一些動(dòng)態(tài)的環(huán)境變量,作為BHContext
的補(bǔ)充存在 -
BHContext
類:是一個(gè)單例啤斗,其內(nèi)部有兩個(gè)NSMutableDictionary
的屬性表箭,分別是modulesByName
和servicesByName
。這個(gè)類主要用來保存上下文信息的争占。例如:application:didFinishLaunchingWithOptions:
的時(shí)候燃逻,就可以初始化大量的上下文信息
-
BHTimeProfiler
類:用來進(jìn)行計(jì)算時(shí)間性能
方面的Profiler
-
BHWatchDog
類:用來開一個(gè)線程序目,監(jiān)聽主線程是否堵塞
最后
幾個(gè)月下來熬夜寫了不少關(guān)于OC底層的文章,這個(gè)過程中對(duì)OC又有了新的認(rèn)知伯襟,OC的內(nèi)容研究目前告于段落猿涨!后面會(huì)分享一些自己在實(shí)際開發(fā)中的覺得比較好的封裝和架構(gòu)思路。我將繼續(xù)探究Swift底層姆怪,也會(huì)繼續(xù)更新文章叛赚,希望大家能夠相互交流,一起進(jìn)步稽揭。謝謝俺附!