OC底層面試題-組件化通信(下)

上篇我們知道了如何創(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í)例的方法

具體使用用法:

模塊間的引用關(guān)系如下:

【優(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ì) targetNameactionName進(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.用OCruntime創(chuàng)建對(duì)象笋婿,不支持Swift
  • 3.只做了protocolclass的匹配誉裆,不支持更復(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

image

主要分三種

  • 1.系統(tǒng)事件:主要是指Application生命周期事件

通常做法是AppDelegate改為繼承自BHAppDelegate

image

  • 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)一步的把implInstanceserviceStr對(duì)應(yīng)的加到BHContextservicesByName字典里面緩存起來堆缘。這樣就可以隨著上下文傳遞了

  • 進(jìn)入serviceImplClass實(shí)現(xiàn)滔灶,從這里可以看出protocol和類是通過字典綁定的,protocol作為key吼肥,serviceImp(類的名字)作為value

Module & Protocol

這里總結(jié)一下:

  • 對(duì)于Module數(shù)組存儲(chǔ)
  • 對(duì)于Protocol:通過字典protocol進(jìn)行綁定录平,keyprotocolvalueserviceImp即類名

輔助類

  • BHConfig類:是一個(gè)單例缀皱,其內(nèi)部有一個(gè)NSMutableDictionary類型的config屬性斗这,該屬性維護(hù)了一些動(dòng)態(tài)的環(huán)境變量,作為BHContext的補(bǔ)充存在
  • BHContext類:是一個(gè)單例啤斗,其內(nèi)部有兩個(gè)NSMutableDictionary的屬性表箭,分別是modulesByNameservicesByName。這個(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)步稽揭。謝謝俺附!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市溪掀,隨后出現(xiàn)的幾起案子事镣,更是在濱河造成了極大的恐慌,老刑警劉巖揪胃,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件璃哟,死亡現(xiàn)場離奇詭異,居然都是意外死亡喊递,警方通過查閱死者的電腦和手機(jī)随闪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骚勘,“玉大人铐伴,你說我怎么就攤上這事∏味铮” “怎么了当宴?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長藐石。 經(jīng)常有香客問我即供,道長定拟,這世上最難降的妖魔是什么于微? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮青自,結(jié)果婚禮上株依,老公的妹妹穿的比我還像新娘。我一直安慰自己延窜,他們只是感情好恋腕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著逆瑞,像睡著了一般荠藤。 火紅的嫁衣襯著肌膚如雪伙单。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天哈肖,我揣著相機(jī)與錄音吻育,去河邊找鬼。 笑死淤井,一個(gè)胖子當(dāng)著我的面吹牛布疼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播币狠,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼游两,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了漩绵?” 一聲冷哼從身側(cè)響起贱案,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎止吐,沒想到半個(gè)月后轰坊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡祟印,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年肴沫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蕴忆。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡颤芬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出套鹅,到底是詐尸還是另有隱情站蝠,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布卓鹿,位于F島的核電站菱魔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏吟孙。R本人自食惡果不足惜澜倦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望杰妓。 院中可真熱鬧藻治,春花似錦、人聲如沸巷挥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至雏节,卻和暖如春胜嗓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背钩乍。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國打工兼蕊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人件蚕。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓孙技,卻偏偏與公主長得像,于是被迫代替她去往敵國和親排作。 傳聞我的和親對(duì)象是個(gè)殘疾皇子牵啦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容