App組件化

原文鏈接: http://casatwy.com/iOS-Modulization.html?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io 作者是大神啊

iOS應用架構談 開篇
iOS應用架構談 view層的組織和調用方案
iOS應用架構談 網絡層設計方案
iOS應用架構談 本地持久化方案及動態(tài)部署
iOS應用架構談 組件化方案

簡述

前幾天的一個晚上在infoQ的微信群里,來自蘑菇街的Limboy做了一個分享冯乘,講了蘑菇街的組件化之路永丝。我不認為這條組件化之路蘑菇街走對了漱办。分享后我私聊了Limboy尿褪,Limboy似乎也明白了問題所在徐伐,我答應他我會把我的方案寫成文章光绕,于是這篇文章就出來了女嘲。

另外,按道理說組件化方案也屬于iOS應用架構談的一部分诞帐,但是當初構思架構談時欣尼,我沒打算寫組件化方案,因為我忘了還有這回事兒停蕉。愕鼓。。后來寫到view的時候才想起來慧起,所以在view的那篇文章最后補了一點內容菇晃。而且覺得這個組件化方案太簡單,包括實現(xiàn)組件化方案的組件也很簡單蚓挤,代碼算上注釋也才100行磺送,我就偷懶放過了,畢竟寫一篇文章好累的啊灿意。

本文的組件化方案demo在這里https://github.com/casatwy/CTMediator 拉下來后記得pod install 拉下來后記得pod install 拉下來后記得pod install
估灿,這個Demo對業(yè)務敏感的邊界情況處理比較簡單,這需要根據(jù)不同App的特性和不同產品的需求才能做缤剧,所以只是為了說明組件化架構用的馅袁。如果要應用在實際場景中的話,可以根據(jù)代碼里給出的注釋稍加修改荒辕,就能用了汗销。

蘑菇街的原文地址在這里:《蘑菇街 App 的組件化之路》犹褒,沒有耐心看完原文的朋友,我在這里簡要介紹一下蘑菇街的組件化是怎么做的:

App啟動時實例化各組件模塊弛针,然后這些組件向ModuleManager注冊Url叠骑,有些時候不需要實例化,使用class注冊钦奋。
當組件A需要調用組件B時座云,向ModuleManager傳遞URL疙赠,參數(shù)跟隨URL以GET方式傳遞付材,類似openURL。然后由ModuleManager負責調度組件B圃阳,最后完成任務厌衔。

這里的兩步中,每一步都存在問題捍岳。

第一步的問題在于富寿,在組件化的過程中,注冊URL并不是充分必要條件锣夹,組件是不需要向組件管理器注冊Url的页徐。而且注冊了Url之后,會造成不必要的內存常駐银萍,如果只是注冊Class变勇,內存常駐量就小一點,如果是注冊實例贴唇,內存常駐量就大了搀绣。至于蘑菇街注冊的是Class還是實例,Limboy分享時沒有說戳气,文章里我也沒看出來链患,也有可能是我看漏了。不過這還并不能算是致命錯誤瓶您,只能算是小缺陷麻捻。

真正的致命錯誤在第二步。在iOS領域里呀袱,一定是組件化的中間件為openUrl提供服務贸毕,而不是openUrl方式為組件化提供服務。

什么意思呢压鉴?

也就是說崖咨,一個App的組件化方案一定不是建立在URL上的,openURL的跨App調用是可以建立在組件化方案上的油吭。當然击蹲,如果App還沒有組件化署拟,openURL方式也是可以建立的,就是丑陋一點而已歌豺。

為什么這么說推穷?

因為組件化方案的實施過程中,需要處理的問題的復雜度类咧,以及拆解馒铃、調度業(yè)務的過程的復雜度比較大,單純以openURL的方式是無法勝任讓一個App去實施組件化架構的痕惋。如果在給App實施組件化方案的過程中是基于openURL的方案的話区宇,有一個致命缺陷:非常規(guī)對象無法參與本地組件間調度。關于非常規(guī)對象
我會在詳細講解組件化方案時有一個辨析值戳。

實際App場景下议谷,如果本地組件間采用GET方式的URL調用,就會產生兩個問題:

根本無法表達非常規(guī)對象

比如你要調用一個圖片編輯模塊堕虹,不能傳遞UIImage到對應的模塊上去的話卧晓,這是一個很悲催的事情。 當然赴捞,這可以通過給方法新開一個參數(shù)逼裆,然后傳遞過去來解決。比如原來是:

[a openUrl:"http://casa.com/detail?id=123&type=0"];

同時就也要提供這樣的方法:

[a openUrl:"http://casa.com/detail" params:@{ @"id":"123", @"type":"0", @"image":[UIImage imageNamed:@"test"]}]

如果不像上面這么做赦政,復雜參數(shù)和非常規(guī)參數(shù)就無法傳遞胜宇。如果這么做了,那么事實上這就是拆分遠程調用和本地調用的入口了昼钻,這就變成了我文章中提倡的做法掸屡,也是蘑菇街方案沒有做到的地方。

另外然评,在本地調用中使用URL的方式其實是不必要的仅财,如果業(yè)務工程師在本地間調度時需要給出URL,那么就不可避免要提供params碗淌,在調用時要提供哪些params是業(yè)務工程師很容易懵逼的地方盏求。。亿眠。在文章下半部分給出的demo代碼樣例已經說明了業(yè)務工程師在本地間調用時碎罚,是不需要知道URL的改抡,而且demo代碼樣例也闡釋了如何解決業(yè)務工程師遇到傳params容易懵逼的問題驾诈。

URL注冊對于實施組件化方案是完全不必要的盐须,且通過URL注冊的方式形成的組件化方案讲仰,拓展性和可維護性都會被打折

注冊URL的目的其實是一個服務發(fā)現(xiàn)的過程资昧,在iOS領域中言缤,服務發(fā)現(xiàn)的方式是不需要通過主動注冊的焦匈,使用runtime就可以了德绿。另外,注冊部分的代碼的維護是一個相對麻煩的事情玫鸟,每一次支持新調用時导绷,都要去維護一次注冊列表。如果有調用被棄用了屎飘,是經常會忘記刪項目的妥曲。runtime由于不存在注冊過程,那就也不會產生維護的操作钦购,維護成本就降低了檐盟。
由于通過runtime做到了服務的自動發(fā)現(xiàn),拓展調用接口的任務就僅在于各自的模塊肮雨,任何一次新接口添加遵堵,新業(yè)務添加箱玷,都不必去主工程做操作怨规,十分透明。

小總結

蘑菇街采用了openURL的方式來進行App的組件化是一個錯誤的做法锡足,使用注冊的方式發(fā)現(xiàn)服務是一個不必要的做法波丰。而且這方案還有其它問題,隨著下文對組件化方案介紹的展開舶得,相信各位自然心里有數(shù)掰烟。

正確的組件化方案

先來看一下方案的架構圖

這幅圖是組件化方案的一個簡化版架構描述,主要是基于Mediator模式和Target-Action模式沐批,中間采用了runtime來完成調用纫骑。這套組件化方案將遠程應用調用和本地應用調用做了拆分,而且是由本地應用調用為遠程應用調用提供服務九孩,與蘑菇街方案正好相反先馆。

調用方式

先說本地應用調用,本地組件A在某處調用

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

向CTMediator發(fā)起跨組件調用躺彬,CTMediator根據(jù)獲得的target和action信息煤墙,通過objective-C的runtime轉化生成target實例以及對應的action選擇子,然后最終調用到目標業(yè)務提供的邏輯宪拥,完成需求仿野。

在遠程應用調用中,遠程應用通過openURL的方式她君,由iOS系統(tǒng)根據(jù)info.plist里的scheme配置找到可以響應URL的應用(在當前我們討論的上下文中脚作,這就是你自己的應用),應用通過AppDelegate接收到URL之后缔刹,調用CTMediator的openUrl:方法將接收到的URL信息傳入球涛。

當然魄梯,CTMediator也可以用openUrl:options:的方式順便把隨之而來的option也接收,這取決于你本地業(yè)務執(zhí)行邏輯時的充要條件是否包含option數(shù)據(jù)宾符。傳入URL之后酿秸,CTMediator通過解析URL,將請求路由到對應的target和action魏烫,隨后的過程就變成了上面說過的本地應用調用的過程了辣苏,最終完成響應。

針對請求的路由操作很少會采用本地文件記錄路由表的方式哄褒,服務端經常處理這種業(yè)務稀蟋,在服務端領域基本上都是通過正則表達式來做路由解析。App中做路由解析可以做得簡單點呐赡,制定URL規(guī)范就也能完成退客,最簡單的方式就是scheme://target/action
這種,簡單做個字符串處理就能把target和action信息從URL中提取出來了链嘀。

組件僅通過Action暴露可調用接口

所有組件都通過組件自帶的Target-Action來響應萌狂,也就是說,模塊與模塊之間的接口被固化在了Target-Action這一層怀泊,避免了實施組件化的改造過程中茫藏,對Business的侵入,同時也提高了組件化接口的可維護性霹琼。

Paste_Image.png

大家可以看到务傲,虛線圈起來的地方就是用于跨組件調用的target和action,這種方式避免了由BusinessA直接提供組件間調用會增加的復雜度枣申,而且任何組件如果想要對外提供調用服務售葡,直接掛上target和action就可以了,業(yè)務本身在大多數(shù)場景下去進行組件化改造時忠藤,是基本不用動的挟伙。

復雜參數(shù)和非常規(guī)參數(shù),以及組件化相關設計思路

這里我們需要針對術語做一個理解上的統(tǒng)一:
復雜參數(shù)
是指由普通類型
的數(shù)據(jù)組成的多層級參數(shù)熄驼。在本文中像寒,我們定義只要是能夠被json解析的類型就都是普通類型
,包括NSNumber瓜贾, NSString诺祸, NSArray, NSDictionary祭芦,以及相關衍生類型筷笨,比如來自系統(tǒng)的NSMutableArray或者你自己定義的都算。
總結一下就是:在本文討論的場景中,復雜參數(shù)的定義是由普通類型組成的具有復雜結構的參數(shù)胃夏。普通類型的定義就是指能夠被json解析的類型轴或。
非常規(guī)參數(shù)
是指由普通類型
以外的類型組成的參數(shù),例如UIImage等這些不能夠被json解析的類型仰禀。然后這些類型組成的參數(shù)在文中就被定義為非常規(guī)參數(shù)
照雁。
總結一下就是:非常規(guī)參數(shù)
是包含非常規(guī)類型的參數(shù)。非常規(guī)類型
的定義就是不能被json解析的類型都叫非常規(guī)類型答恶。

邊界情況:

假設多層級參數(shù)中有存在任何一個內容是非常規(guī)參數(shù)饺蚊,本文中這種參數(shù)就也被認為是非常規(guī)參數(shù)。

如果某個類型當前不能夠被json解析悬嗓,但通過某種轉化方式能夠轉化成json污呼,那么這種類型在場景上下文中,我們也稱為普通類型包竹。

舉個例子就是通過json描述的自定義view燕酷。如果這個view能夠通過某個組件被轉化成json,那么即使這個view本身并不是普通類型周瞎,在具有轉化器的上下文場景中苗缩,我們依舊認為它是普通類型。

如果上下文場景中沒有轉化器堰氓,這個view就是非常規(guī)類型了挤渐。

假設轉化出的json不能夠被還原成view,比如組件A有轉化器双絮,組件B中沒有轉化器,因此在組件間調用過程中json在B組件里不能被還原成view得问。在這種調用方向中囤攀,只要調用者能將非常規(guī)類型轉化成json的,我們就依然認為這個view是普通類型宫纬。如果調用者是組件A焚挠,轉化器在組件B中,A傳遞view參數(shù)時是沒辦法轉化成json的漓骚,那么這個view就被認為是非常規(guī)類型蝌衔,哪怕它在組件B中能夠被轉化成json。

然后我來解釋一下為什么應該由本地組件間調用來支持遠程應用調用:

在遠程App調用時蝌蹂,遠程App是不可能通過URL來提供非常規(guī)參數(shù)的噩斟,最多只能以json string的方式經過URLEncode之后再通過GET來提供復雜參數(shù),然后再在本地組件中解析json孤个,最終完成調用剃允。在組件間調用時,通過performTarget:action:params:
是能夠提供非常規(guī)參數(shù)的,于是我們可以知道斥废,遠程App調用
時的上下文環(huán)境以及功能是本地組件間調用
時上下文環(huán)境以及功能的子集
椒楣。

因此這個邏輯注定了必須由本地組件間調用來為遠程App調用來提供服務,只有符合這個邏輯的設計思路才是正確的組件化方案的設計思路牡肉,其他跟這個不一致的思路一定就是錯的捧灰。因為邏輯上子集為父集提供服務說不通,所以強行這么做的話统锤,用一個成語來總結就叫做倒行逆施凤壁。

另外,遠程App調用和本地組件間調用必須要拆分開跪另,遠程App調用只能走CTMediator
提供的專用遠程的方法拧抖,本地組件間調用只能走CTMediator
提供的專用本地的方法,兩者不能通過同一個接口來調用免绿。
這里有兩個原因:

遠程App調用處理入?yún)⒌倪^程比本地多了一個URL解析的過程唧席,這是遠程App調用特有的過程。這一點我前面說過嘲驾,這里我就不細說了淌哟。

架構師沒有充要條件條件可以認為遠程App調用對于無響應請求的處理方式和本地組件間調用無響應請求的處理方式在未來產品的演進過程中是一致的

在遠程App調用中,用戶通過url進入app辽故,當app無法為這個url提供服務時徒仓,常見的辦法是展示一個所謂的404界面,告訴用戶"當前沒有相對應的內容誊垢,不過你可以在app里別的地方再逛逛"掉弛。這個場景多見于用戶使用的App版本不一致。比如有一個URL只有1.1版本的app能完整響應喂走,1.0版本的app雖然能被喚起殃饿,但是無法完成整個響應過程,那么1.0的app就要展示一個404了芋肠。

在組件間調用中乎芳,如果遇到了無法響應的請求,就要分兩種場景考慮了帖池。

場景1

如果這種無法響應的請求發(fā)生場景是在開發(fā)過程中奈惑,比如兩個組件同時在開發(fā),組件A調用組件B時睡汹,組件B還處于舊版本沒有發(fā)布新版本肴甸,因此響應不了,那么這時候的處理方式可以相對隨意帮孔,只要能體現(xiàn)B模塊是舊版本就行了雷滋,最后在RC階段統(tǒng)測時是一定能夠發(fā)現(xiàn)的不撑,只要App沒發(fā)版,怎么處理都來得及晤斩。

場景2

如果這種無法響應的請求發(fā)生場景是在已發(fā)布的App中焕檬,有可能展示個404就結束了,那這就跟遠程App調用時的404處理場景一樣澳泵。但也有可能需要為此做一些額外的事情实愚,有可能因為做了額外的事情,就不展示404了兔辅,展示別的頁面了腊敲,這一切取決于產品經理。

那么這種場景是如何發(fā)生的呢维苔?

我舉一個例子:當用戶在1.0版本時收藏了一個東西碰辅,然后用戶升級App到1.1版本。1.0版本的收藏項目在本地持久層存入的數(shù)據(jù)有可能是會跟1.1版本收藏時存入的數(shù)據(jù)是不一致的介时。此時用戶在1.1版本的app中對1.0版本收藏的東西做了一些操作没宾,觸發(fā)了本地組件間調用,這個本地間調用又與收藏項目本身的數(shù)據(jù)相關沸柔,那么這時這個調用就是有可能變成無響應調用循衰,此時的處理方式就不見得跟以前一樣展示個404頁面就結束了,因為用戶已經看到了收藏了的東西褐澎,結果你還告訴他找不到会钝,用戶立刻懵逼。工三。迁酸。這時候的處理方式就會用很多種,至于產品經理會選擇哪種徒蟆,你作為架構師是沒有辦法預測的胁出。如果產品經理提的需求落實到架構上,對調用入口產生要求然而你的架構又沒有拆分調用入口段审,對于你的選擇就只有兩個:要么打回產品需求,要么加個班去拆分調用入口闹蒜。

當然寺枉,架構師可以選擇打回產品經理的需求,最終挑選一個自己的架構能夠承載的需求绷落。但是姥闪,如果這種是因為你早期設計架構時挖的坑而打回的產品需求,你不覺得丟臉么砌烁?

鑒于遠程app調用和本地組件間調用下的無響應請求處理方式不同筐喳,以及未來不可知的產品演進催式,拆分遠程app調用入口和本地組件間調用入口是功在當代利在千秋的事情。

組件化方案中的去model設計

組件間調用時避归,是需要針對參數(shù)做去model化的荣月。如果組件間調用不對參數(shù)做去model化的設計,就會導致業(yè)務形式上被組件化了梳毙,實質上依然沒有被獨立
哺窄。

假設模塊A和模塊B之間采用model化的方案去調用,那么調用方法時傳遞的參數(shù)就會是一個對象账锹。

如果對象不是一個面向接口的通用對象萌业,那么mediator的參數(shù)處理就會非常復雜,因為要區(qū)分不同的對象類型奸柬。如果mediator不處理參數(shù)生年,直接將對象以范型的方式轉交給模塊B,那么模塊B必然要包含對象類型的聲明廓奕。假設對象聲明放在模塊A抱婉,那么B和A之間的組件化只是個形式主義。如果對象類型聲明放在mediator懂从,那么對于B而言授段,就不得不依賴mediator。但是番甩,大家可以從上面的架構圖中看到侵贵,對于響應請求的模塊而言,依賴mediator并不是必要條件缘薛,因此這種依賴是完全不需要的窍育,這種依賴的存在對于架構整體而言,是一種污染宴胧。

如果參數(shù)是一個面向接口的對象漱抓,那么mediator對于這種參數(shù)的處理其實就沒必要了,更多的是直接轉給響應方的模塊恕齐。而且接口的定義就不可能放在發(fā)起方的模塊中了乞娄,只能放在mediator中。響應方如果要完成響應显歧,就也必須要依賴mediator仪或,然而前面我已經說過,響應方對于mediator的依賴是不必要的士骤,因此參數(shù)其實也并不適合以面向接口的對象的方式去傳遞范删。

因此,使用對象化的參數(shù)無論是否面向接口拷肌,帶來的結果就是業(yè)務模塊形式上是被組件化了到旦,但實質上依然沒有被獨立旨巷。

在這種跨模塊場景中,參數(shù)最好還是以去model化的方式去傳遞添忘,在iOS的開發(fā)中采呐,就是以字典的方式去傳遞。這樣就能夠做到只有調用方依賴mediator昔汉,而響應方不需要依賴mediator懈万。然而在去model化的實踐中,由于這種方式自由度太大靶病,我們至少需要保證調用方生成的參數(shù)能夠被響應方理解会通,然而在組件化場景中,限制去model化方案的自由度的手段娄周,相比于網絡層和持久層更加容易得多涕侈。

因為組件化天然具備了限制手段:參數(shù)不對就無法調用!無法調用時直接debug就能很快找到原因煤辨。所以接下來要解決的去model化方案的另一個問題就是:如何提高開發(fā)效率裳涛。

在去model的組件化方案中,影響效率的點有兩個:調用方如何知道接收方需要哪些key的參數(shù)众辨?調用方如何知道有哪些target可以被調用端三?其實后面的那個問題不管是不是去model的方案,都會遇到鹃彻。為什么放在一起說郊闯,因為我接下來要說的解決方案可以把這兩個問題一起解決。

解決方案就是使用category

mediator這個repo維護了若干個針對mediator的category蛛株,每一個對應一個target团赁,每個category里的方法對應了這個target下所有可能的調用場景,這樣調用者在包含mediator的時候谨履,自動獲得了所有可用的target-action欢摄,無論是調用還是參數(shù)傳遞,都非常方便笋粟。接下來我要解釋一下為什么是category而不是其他:

category本身就是一種組合模式怀挠,根據(jù)不同的分類提供不同的方法,此時每一個組件就是一個分類害捕,因此把每個組件可以支持的調用用category封裝是很合理的唆香。

在category的方法中可以做到參數(shù)的驗證,在架構中對于保證參數(shù)安全是很有必要的吨艇。當參數(shù)不對時,category就提供了補救的入口腾啥。

category可以很輕松地做請求轉發(fā)东涡,如果不采用category冯吓,請求轉發(fā)邏輯就非常難做了。

category統(tǒng)一了所有的組件間調用入口疮跑,因此無論是在調試還是源碼閱讀上组贺,都為工程師提供了極大的方便。

由于category統(tǒng)一了所有的調用入口祖娘,使得在跨模塊調用時失尖,對于param的hardcode在整個App中的作用域僅存在于category中,在這種場景下的hardcode就已經變成和調用宏或者調用聲明沒有任何區(qū)別了渐苏,因此是可以接受的掀潮。

這里是業(yè)務方使用category調用時的場景,大家可以看到非常方便琼富,不用去記URL也不用糾結到底應該傳哪些參數(shù)仪吧。

if (indexPath.row == 0) { 
    UIViewController *viewController = [[CTMediator sharedInstance] CTMediator_viewControllerForDetail]; 
    // 獲得view controller之后,在這種場景下鞠眉,到底push還是present薯鼠,其實是要由使用者決定的,mediator只要給出view controller的實例就好了 
    [self presentViewController:viewController animated:YES completion:nil]; 
} 
if (indexPath.row == 1) { 
    UIViewController *viewController = [[CTMediator sharedInstance] CTMediator_viewControllerForDetail];
    [self.navigationController pushViewController:viewController animated:YES]; 
} if (indexPath.row == 2) { 
    // 這種場景下械蹋,很明顯是需要被present的出皇,所以不必返回實例,mediator直接present了
    [[CTMediator sharedInstance] CTMediator_presentImage:[UIImage imageNamed:@"image"]];
 } 
if (indexPath.row == 3) { 
    // 這種場景下哗戈,參數(shù)有問題郊艘,因此需要在流程中做好處理 
    [[CTMediator sharedInstance] CTMediator_presentImage:nil]; 
} 
if (indexPath.row == 4) { 
    [[CTMediator sharedInstance] CTMediator_showAlertWithMessage:@"casa" cancelAction:nil confirmAction:^(NSDictionary *info) {
    // 做你想做的事
    NSLog(@"%@", info); 
    }]; 
}

本文對應的demo展示了如何使用category來實現(xiàn)去model的組件調用。上面的代碼片段也是摘自這個demo谱醇。

基于其他考慮還要再做的一些額外措施

基于安全考慮

我們需要防止黑客通過URL的方式調用本屬于native的組件暇仲,比如支付寶的個人財產頁面。如果在調用層級上沒有區(qū)分好副渴,沒有做好安全措施奈附,黑客就有通過safari查看任何人的個人財產的可能。

安全措施其實有很多煮剧,大部分取決于App本身以及產品的要求斥滤。在架構層面要做的最基礎的一點就是區(qū)分調用是來自于遠程App還是本地組件,我在demo中的安全措施是采用給action添加native
前綴去做的勉盅,凡是帶有native前綴的就都只允許本地組件調用佑颇,如果在url階段發(fā)現(xiàn)調用了前綴為native的方法,那就可以采取響應措施了草娜。這也是將遠程app調用入口和本地組件調用入口區(qū)分開來的重要原因之一挑胸。
當然,為了確保安全的做法有很多宰闰,但只要拆出遠程調用和本地調用茬贵,各種做法就都有施展的空間了簿透。

基于動態(tài)調度考慮

動態(tài)調度的意思就是,今天我可能這個跳轉是要展示A頁面解藻,但是明天可能同樣的跳轉就要去展示B頁面了老充。這個跳轉有可能是來自于本地組件間跳轉也有可能是來自于遠程app。

做這個事情的切點在本文架構中螟左,有很多個:

以url parse為切點
以實例化target時為切點
以category調度方法為切點
以target下的action為切點

如果以url parse為切點
的話啡浊,那么這個動態(tài)調度就只能夠對遠程App跳轉產生影響,失去了動態(tài)調度本地跳轉的能力胶背,因此是不適合的巷嚣。

如果以實例化target時為切點
的話,就需要在代碼中針對所有target都做一次審查奄妨,看是否要被調度涂籽,這是沒必要的。假設10個調用請求中砸抛,只有1個要被動態(tài)調度评雌,那么就必須要審查10次,只有那1次審查通過了直焙,才走動態(tài)調度景东,這是一種相對比較粗暴的方法。

如果以category調度方法為切點
的話奔誓,那動態(tài)調度就只能影響到本地件組件的跳轉斤吐,因為category是只有本地才用的,所以也不適合厨喂。

以target下的action為切點
是最適合的和措,因為動態(tài)調度在一般場景下都是有范圍的,大多數(shù)是活動頁需要動態(tài)調度蜕煌,今天這個活動明天那個活動派阱,或者今天活動正在進行明天活動就結束了,所以產生動態(tài)調度的需求斜纪。我們在可能產生動態(tài)調度的action中審查當前action是否需要被動態(tài)調度贫母,在常規(guī)調度中就沒必要審查了,例如個人主頁的跳轉盒刚,商品詳情的跳轉等腺劣,這樣效率就能比較高。

大家會發(fā)現(xiàn)因块,如果要做類似這種效率更高的動態(tài)調度橘原,target-action層被抽象出來就是必不可少的,然而蘑菇街并沒有抽象出target-action層,這也是其中的一個問題靠柑。

當然寨辩,如果你的產品要求所有頁面都是存在動態(tài)調度需求的,那就還是以實例化target時為切點
去調度了歼冰,這樣能做到審查每一次調度請求,從而實現(xiàn)動態(tài)調度耻警。

說完了調度切點隔嫡,接下來要說的就是如何完成審查流程。完整的審查流程有幾種甘穿,我每個都列舉一下:

App啟動時下載調度列表腮恩,或者定期下載調度列表。然后審查時檢查當前action是否存在要被動態(tài)調度跳轉的action温兼,如果存在秸滴,則跳轉到另一個action
每一次到達新的action時,以action為參數(shù)調用API獲知是否需要被跳轉募判,如果需要被跳轉荡含,則API告知要跳轉的action,然后再跳轉到API指定的action

這兩種做法其實都可以届垫,如果產品對即時性的要求比較高释液,那么采用第二種方案,如果產品對即時性要求不那么高装处,第一種方案就可以了误债。由于本文的方案是沒有URL注冊列表的,因此服務器只要給出原始target-action和對應跳轉的target-action就可以了妄迁,整個流程不是只有注冊URL列表才能達成的寝蹈,而且這種方案比注冊URL列表要更易于維護一些。

另外登淘,說采用url rewrite的手段來進行動態(tài)調度箫老,也不是不可以。但是這里我需要辨析的是形帮,URL的必要性僅僅體現(xiàn)在遠程App調度中槽惫,是沒必要蔓延到本地組件間調用的。這樣辩撑,當我們做遠程App的URL路由時(目前的demo沒有提供URL路由功能界斜,但是提供了URL路由操作的接入點,可以根據(jù)業(yè)務需求插入這個功能)合冀,要關心的事情就能少很多各薇,可以比較干凈。在這種場景下,單純以URL rewrite的方式其實就與上文提到的以url parse為切點
沒有區(qū)別了峭判。

相比之下开缎,蘑菇街的組件化方案有以下缺陷

蘑菇街沒有拆分遠程調用和本地間調用

不拆分遠程調用和本地間調用,就使得后續(xù)很多手段難以實施林螃,這個我在前文中都已經有論述了奕删。另外再補充一下,這里的拆分不是針對來源做拆分疗认。比如通過URL來區(qū)分是遠程App調用還是本地調用完残,這只是區(qū)分了調用者的來源。

這里說的區(qū)分是指:遠程調用走遠程調用路徑横漏,也就是openUrl>urlParse->perform->target-action谨设。
本地組件間調用就走本地組件間調用路徑:perform->target-action。
這兩個是一定要作區(qū)分的缎浇,蘑菇街方案并沒有對此做好區(qū)分扎拣。

蘑菇街以遠程調用的方式為本地間調用提供服務

這是本末倒置的做法,倒行逆施導致的是未來架構難以為業(yè)務發(fā)展提供支撐素跺。因為前面已經論述過二蓝,在iOS場景下,遠程調用的實現(xiàn)是本地調用實現(xiàn)的子集亡笑,只有大的為小提供服務侣夷,也就是本地調用為遠程調用提供服務,如果反過來就是倒行逆施了仑乌。

蘑菇街的本地間調用無法傳遞非常規(guī)參數(shù)百拓,復雜參數(shù)的傳遞方式非常丑陋

注意這里復雜參數(shù)
和非常規(guī)參數(shù)
的辨析。

由于采用遠程調用的方式執(zhí)行本地調用晰甚,在前面已經論述過兩者功能集的關系衙传,因此這種做法無法滿足傳遞非常規(guī)參數(shù)的需求。而且如果基于這種方式不變的話厕九,復雜參數(shù)的傳遞也只能依靠經過urlencode的json string進行蓖捶,這種方式非常丑陋,而且也不便于調試扁远。

蘑菇街必須要在app啟動時注冊URL響應者

這個條件在組件化方案中是不必要條件俊鱼,demo也已經證實了這一點。這個不必要的操作會導致不必要的維護成本畅买,如果單純從只要完成業(yè)務就好
的角度出發(fā)并闲,這倒不是什么大問題。這就看架構師對自己是不是要求嚴格了谷羞。

新增組件化的調用路徑時帝火,蘑菇街的操作相對復雜

在本文給出的組件化方案中,響應者唯一要做的事情就是提供Target和Action,并不需要再做其它的事情犀填。蘑菇街除此之外還要再做很多額外不必要措施蠢壹,才能保證調用成功。

蘑菇街沒有針對target層做封裝

這種做法使得所有的跨組件調用請求直接hit到業(yè)務模塊九巡,業(yè)務模塊必然因此變得臃腫難以維護图贸,屬于侵入式架構。應該將原本屬于調用相應的部分拿出來放在target-action中比庄,才能盡可能保證不將無關代碼侵入到原有業(yè)務組件中求妹,才能保證業(yè)務組件未來的遷移和修改不受組件調用的影響,以及降低為項目的組件化實施而帶來的時間成本佳窑。

總結

本文提供的組件化方案是采用Mediator模式和蘋果體系下的Target-Action模式設計的。

然而這款方案有一個很小的缺陷在于對param的key的hardcode父能,這是為了達到最大限度的解耦和靈活度而做的權衡神凑。在我的網絡層架構和持久層架構中,都沒有hardcode的場景何吝,這也從另一個側面說明了組件化架構的特殊性溉委。

權衡時,考慮到這部分hardcode的影響域僅僅存在于mediator的category中
爱榕。在這種情況下瓣喊,hardcode對于調用者的調用是完全透明的。對于響應者而言黔酥,處理方式等價于對API返回的參數(shù)的處理方式藻三,且響應者的處理方式也被限制在了Action中

因此這部分的hardcode的存在雖然確實有點不干凈跪者,但是相比于這些不干凈而帶來的其他好處而言棵帽,在權衡時是可以接受的,如果不采用hardcode渣玲,那勢必就會導致請求響應方也需要依賴mediator
逗概,然而這在邏輯上是不必要
的。另外忘衍,在我的各個項目的實際使用過程中逾苫,這部分hardcode是沒有影響的。

另外要談的是枚钓,之所以會在組件化方案中出現(xiàn)harcode铅搓,而網絡層和持久層的去model化都沒有發(fā)生hardcode情況,是因為組件化調用的所有接受者和調用者都在同一片上下文里秘噪。網絡層有一方在服務端狸吞,持久層有一方在數(shù)據(jù)庫。再加上設計時針對hardcode部分的改進手段其實已經超出了語言本身的限制。也就是說蹋偏,harcode受限于語言本身便斥。objective-C也好,swift也好威始,它們的接口設計哲學是存在缺陷的枢纠。如果我們假設在golang的背景下,是完全可以用golang的接口體系去做一個最優(yōu)美的架構方案出來的黎棠。不過這已經不屬于本文的討論范圍了晋渺,有興趣的同學可以去了解一下相關知識。架構設計有時就是這么無奈脓斩。

組件化方案在App業(yè)務穩(wěn)定木西,且規(guī)模(業(yè)務規(guī)模和開發(fā)團隊規(guī)模)增長初期去實施非常重要宠互,它助于將復雜App分而治之君珠,也有助于多人大型團隊的協(xié)同開發(fā)。但組件化方案
不適合在業(yè)務不穩(wěn)定的情況下過早實施袖裕,至少要等產品已經經過MVP階段時才適合實施組件化燎猛。因為業(yè)務不穩(wěn)定意味著鏈路不穩(wěn)定恋捆,在不穩(wěn)定的鏈路上實施組件化會導致將來主業(yè)務產生變化時,全局性模塊調度和重構會變得相對復雜重绷。
當決定要實施組件化方案時沸停,對于組件化方案的架構設計優(yōu)劣直接影響到架構體系能否長遠地支持未來業(yè)務的發(fā)展,對App的組件化不只是僅僅的拆代碼和跨業(yè)務調頁面
昭卓,還要考慮復雜和非常規(guī)業(yè)務參數(shù)參與的調度愤钾,非頁面的跨組件功能調度,組件調度安全保障葬凳,組件間解耦绰垂,新舊業(yè)務的調用接口修改等問題。

蘑菇街的組件化方案只實現(xiàn)了跨業(yè)務頁面調用的需求火焰,本質上只實現(xiàn)了我在view層架構的文章中跨業(yè)務頁面調用的內容劲装,這還沒有到成為組件化方案
的程度,且蘑菇街的組件化方案距離真正的App組件化的要求還是差了一段距離的昌简,且存在設計邏輯缺陷占业,希望蘑菇街能夠加緊重構,打造真正的組件化方案纯赎。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末谦疾,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子犬金,更是在濱河造成了極大的恐慌念恍,老刑警劉巖六剥,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異峰伙,居然都是意外死亡疗疟,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門瞳氓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來策彤,“玉大人,你說我怎么就攤上這事匣摘〉晔” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵音榜,是天一觀的道長庞瘸。 經常有香客問我,道長赠叼,這世上最難降的妖魔是什么恕洲? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮梅割,結果婚禮上,老公的妹妹穿的比我還像新娘葛家。我一直安慰自己户辞,他們只是感情好,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布癞谒。 她就那樣靜靜地躺著底燎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪弹砚。 梳的紋絲不亂的頭發(fā)上双仍,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音桌吃,去河邊找鬼朱沃。 笑死,一個胖子當著我的面吹牛茅诱,可吹牛的內容都是我干的逗物。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼瑟俭,長吁一口氣:“原來是場噩夢啊……” “哼翎卓!你這毒婦竟也來了?” 一聲冷哼從身側響起摆寄,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤失暴,失蹤者是張志新(化名)和其女友劉穎坯门,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逗扒,經...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡古戴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了缴阎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片允瞧。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蛮拔,靈堂內的尸體忽然破棺而出述暂,到底是詐尸還是另有隱情,我是刑警寧澤建炫,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布畦韭,位于F島的核電站,受9級特大地震影響肛跌,放射性物質發(fā)生泄漏艺配。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一衍慎、第九天 我趴在偏房一處隱蔽的房頂上張望转唉。 院中可真熱鬧,春花似錦稳捆、人聲如沸赠法。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽砖织。三九已至,卻和暖如春末荐,著一層夾襖步出監(jiān)牢的瞬間侧纯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工甲脏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留眶熬,地道東北人。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓剃幌,卻偏偏與公主長得像聋涨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子负乡,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內容