大V 的 ios 組件化方案

大V 的 ios 組件化方案 怕以后找不到滤港,在這個做個記錄
原貼地址 https://casatwy.com/iOS-Modulization.html

iOS應(yīng)用架構(gòu)談 開篇
iOS應(yīng)用架構(gòu)談 view層的組織和調(diào)用方案
iOS應(yīng)用架構(gòu)談 網(wǎng)絡(luò)層設(shè)計方案
iOS應(yīng)用架構(gòu)談 本地持久化方案及動態(tài)部署
iOS應(yīng)用架構(gòu)談 組件化方案

簡述

前幾天的一個晚上在infoQ的微信群里,來自蘑菇街的Limboy做了一個分享掏父,講了蘑菇街的組件化之路岸军。我不認為這條組件化之路蘑菇街走對了扣蜻。分享后我私聊了Limboy,Limboy似乎也明白了問題所在,我答應(yīng)他我會把我的方案寫成文章冬三,于是這篇文章就出來了。

另外缘缚,按道理說組件化方案也屬于iOS應(yīng)用架構(gòu)談的一部分勾笆,但是當初構(gòu)思架構(gòu)談時,我沒打算寫組件化方案桥滨,因為我忘了還有這回事兒窝爪。弛车。。后來寫到view的時候才想起來蒲每,所以在view的那篇文章最后補了一點內(nèi)容纷跛。而且覺得這個組件化方案太簡單,包括實現(xiàn)組件化方案的組件也很簡單啃勉,代碼算上注釋也才100行忽舟,我就偷懶放過了,畢竟寫一篇文章好累的啊淮阐。

本文的組件化方案demo在這里https://github.com/casatwy/CTMediator 拉下來后記得pod install 拉下來后記得pod install 拉下來后記得pod install叮阅,這個Demo對業(yè)務(wù)敏感的邊界情況處理比較簡單,這需要根據(jù)不同App的特性和不同產(chǎn)品的需求才能做泣特,所以只是為了說明組件化架構(gòu)用的浩姥。如果要應(yīng)用在實際場景中的話,可以根據(jù)代碼里給出的注釋稍加修改状您,就能用了勒叠。

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

  1. App啟動時實例化各組件模塊眯分,然后這些組件向ModuleManager注冊Url,有些時候不需要實例化柒桑,使用class注冊弊决。
  2. 當組件A需要調(diào)用組件B時,向ModuleManager傳遞URL魁淳,參數(shù)跟隨URL以GET方式傳遞飘诗,類似openURL。然后由ModuleManager負責調(diào)度組件B界逛,最后完成任務(wù)昆稿。

這里的兩步中,每一步都存在問題息拜。

第一步的問題在于溉潭,在組件化的過程中,注冊URL并不是充分必要條件该溯,組件是不需要向組件管理器注冊Url的岛抄。而且注冊了Url之后,會造成不必要的內(nèi)存常駐狈茉,如果只是注冊Class夫椭,內(nèi)存常駐量就小一點,如果是注冊實例氯庆,內(nèi)存常駐量就大了蹭秋。至于蘑菇街注冊的是Class還是實例扰付,Limboy分享時沒有說,文章里我也沒看出來仁讨,也有可能是我看漏了羽莺。不過這還并不能算是致命錯誤,只能算是小缺陷洞豁。

真正的致命錯誤在第二步盐固。在iOS領(lǐng)域里,一定是組件化的中間件為openUrl提供服務(wù)丈挟,而不是openUrl方式為組件化提供服務(wù)刁卜。

什么意思呢?

也就是說曙咽,一個App的組件化方案一定不是建立在URL上的蛔趴,openURL的跨App調(diào)用是可以建立在組件化方案上的。當然例朱,如果App還沒有組件化孝情,openURL方式也是可以建立的,就是丑陋一點而已洒嗤。

為什么這么說箫荡?

因為組件化方案的實施過程中,需要處理的問題的復(fù)雜度渔隶,以及拆解菲茬、調(diào)度業(yè)務(wù)的過程的復(fù)雜度比較大,單純以openURL的方式是無法勝任讓一個App去實施組件化架構(gòu)的派撕。如果在給App實施組件化方案的過程中是基于openURL的方案的話,有一個致命缺陷:非常規(guī)對象無法參與本地組件間調(diào)度睬魂。關(guān)于非常規(guī)對象我會在詳細講解組件化方案時有一個辨析终吼。

實際App場景下,如果本地組件間采用GET方式的URL調(diào)用氯哮,就會產(chǎn)生兩個問題:

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

比如你要調(diào)用一個圖片編輯模塊际跪,不能傳遞UIImage到對應(yīng)的模塊上去的話,這是一個很悲催的事情喉钢。 當然姆打,這可以通過給方法新開一個參數(shù),然后傳遞過去來解決肠虽。比如原來是:

<pre style="box-sizing: border-box; overflow: auto; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13px; display: block; padding: 9.5px; margin: 0px 0px 10px; line-height: 1.42857; color: rgb(208, 208, 208); word-break: break-all; overflow-wrap: break-word; background: rgb(32, 32, 32); border: 1px solid rgb(204, 204, 204); border-radius: 4px;">[a openUrl:"[http://casa.com/detail?id=123&type=0](http://casa.com/detail?id=123&type=0)"]; </pre>

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

<pre style="box-sizing: border-box; overflow: auto; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13px; display: block; padding: 9.5px; margin: 0px 0px 10px; line-height: 1.42857; color: rgb(208, 208, 208); word-break: break-all; overflow-wrap: break-word; background: rgb(32, 32, 32); border: 1px solid rgb(204, 204, 204); border-radius: 4px;">[a openUrl:"[http://casa.com/detail](http://casa.com/detail)" params:@{ @"id":"123", @"type":"0", @"image":[UIImage imageNamed:@"test"] }] </pre>

如果不像上面這么做幔戏,復(fù)雜參數(shù)和非常規(guī)參數(shù)就無法傳遞。如果這么做了税课,那么事實上這就是拆分遠程調(diào)用和本地調(diào)用的入口了闲延,這就變成了我文章中提倡的做法痊剖,也是蘑菇街方案沒有做到的地方。

另外垒玲,在本地調(diào)用中使用URL的方式其實是不必要的陆馁,如果業(yè)務(wù)工程師在本地間調(diào)度時需要給出URL,那么就不可避免要提供params合愈,在調(diào)用時要提供哪些params是業(yè)務(wù)工程師很容易懵逼的地方叮贩。。佛析。在文章下半部分給出的demo代碼樣例已經(jīng)說明了業(yè)務(wù)工程師在本地間調(diào)用時益老,是不需要知道URL的,而且demo代碼樣例也闡釋了如何解決業(yè)務(wù)工程師遇到傳params容易懵逼的問題说莫。

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

注冊URL的目的其實是一個服務(wù)發(fā)現(xiàn)的過程储狭,在iOS領(lǐng)域中互婿,服務(wù)發(fā)現(xiàn)的方式是不需要通過主動注冊的,使用runtime就可以了辽狈。另外慈参,注冊部分的代碼的維護是一個相對麻煩的事情,每一次支持新調(diào)用時刮萌,都要去維護一次注冊列表驮配。如果有調(diào)用被棄用了,是經(jīng)常會忘記刪項目的着茸。runtime由于不存在注冊過程壮锻,那就也不會產(chǎn)生維護的操作,維護成本就降低了涮阔。

由于通過runtime做到了服務(wù)的自動發(fā)現(xiàn)猜绣,拓展調(diào)用接口的任務(wù)就僅在于各自的模塊,任何一次新接口添加敬特,新業(yè)務(wù)添加掰邢,都不必去主工程做操作,十分透明伟阔。

小總結(jié)

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

正確的組件化方案

先來看一下方案的架構(gòu)圖

<pre style="box-sizing: border-box; overflow: auto; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13px; display: block; padding: 9.5px; margin: 0px 0px 10px; line-height: 1.42857; color: rgb(208, 208, 208); word-break: break-all; overflow-wrap: break-word; background: rgb(32, 32, 32); border: 1px solid rgb(204, 204, 204); border-radius: 4px;"> -------------------------------------- | [CTMediator sharedInstance] | | | | openUrl: <<<<<<<<< (AppDelegate) <<<< Call From Other App With URL | | | | | | | | | |/ | | | | parseUrl | | | | | | | | | .................................|............................... | | | | | | | |/ | | | | performTarget:action:params: <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Call From Native Module | | | | | | | | | | | | |/ | | | | ------------- | | | | | | | runtime | | | | | | | ------------- | | . . | ---------------.---------.------------ . . . . . . . . . . . . . . . . -------------------.----------- ----------.--------------------- | . | | . | | . | | . | | . | | . | | . | | . | | | | | | Target | | Target | | | | | | / | \ | | / | \ | | / | \ | | / | \ | | | | | | Action Action Action ... | | Action Action Action ... | | | | | | | | | | | | | |Business A | | Business B | ------------------------------- -------------------------------- </pre>

這幅圖是組件化方案的一個簡化版架構(gòu)描述娃承,主要是基于Mediator模式和Target-Action模式奏夫,中間采用了runtime來完成調(diào)用怕篷。這套組件化方案將遠程應(yīng)用調(diào)用和本地應(yīng)用調(diào)用做了拆分,而且是由本地應(yīng)用調(diào)用為遠程應(yīng)用調(diào)用提供服務(wù)酗昼,與蘑菇街方案正好相反廊谓。

調(diào)用方式

先說本地應(yīng)用調(diào)用,本地組件A在某處調(diào)用[[CTMediator sharedInstance] performTarget:targetName action:actionName params:@{...}]CTMediator發(fā)起跨組件調(diào)用麻削,CTMediator根據(jù)獲得的target和action信息蒸痹,通過objective-C的runtime轉(zhuǎn)化生成target實例以及對應(yīng)的action選擇子,然后最終調(diào)用到目標業(yè)務(wù)提供的邏輯呛哟,完成需求叠荠。

在遠程應(yīng)用調(diào)用中,遠程應(yīng)用通過openURL的方式扫责,由iOS系統(tǒng)根據(jù)info.plist里的scheme配置找到可以響應(yīng)URL的應(yīng)用(在當前我們討論的上下文中榛鼎,這就是你自己的應(yīng)用),應(yīng)用通過AppDelegate接收到URL之后鳖孤,調(diào)用CTMediatoropenUrl:方法將接收到的URL信息傳入者娱。當然,CTMediator也可以用openUrl:options:的方式順便把隨之而來的option也接收苏揣,這取決于你本地業(yè)務(wù)執(zhí)行邏輯時的充要條件是否包含option數(shù)據(jù)黄鳍。傳入URL之后,CTMediator通過解析URL平匈,將請求路由到對應(yīng)的target和action框沟,隨后的過程就變成了上面說過的本地應(yīng)用調(diào)用的過程了,最終完成響應(yīng)增炭。

針對請求的路由操作很少會采用本地文件記錄路由表的方式忍燥,服務(wù)端經(jīng)常處理這種業(yè)務(wù),在服務(wù)端領(lǐng)域基本上都是通過正則表達式來做路由解析隙姿。App中做路由解析可以做得簡單點灾前,制定URL規(guī)范就也能完成,最簡單的方式就是scheme://target/action這種孟辑,簡單做個字符串處理就能把target和action信息從URL中提取出來了。

組件僅通過Action暴露可調(diào)用接口

所有組件都通過組件自帶的Target-Action來響應(yīng)蔫敲,也就是說饲嗽,模塊與模塊之間的接口被固化在了Target-Action這一層,避免了實施組件化的改造過程中奈嘿,對Business的侵入貌虾,同時也提高了組件化接口的可維護性。

<pre style="box-sizing: border-box; overflow: auto; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13px; display: block; padding: 9.5px; margin: 0px 0px 10px; line-height: 1.42857; color: rgb(208, 208, 208); word-break: break-all; overflow-wrap: break-word; background: rgb(32, 32, 32); border: 1px solid rgb(204, 204, 204); border-radius: 4px;"> -------------------------------- | | | Business A | | | --- ---------- ---------- --- | | | | | | | | | | | | ...........| |........| |........| |........... . | | | | | | . . | | | | | | . . --- --- --- --- --- --- . . | | | | | | . . |action| |action| |action| . . | | | | | | . . ---|---- -----|-- --|----- . . | | | . . | | | . . ----|------ --|--------|-- . . | | | | . . |Target_A1| | Target_A2 | . . | | | | . . ----------- -------------- . . . . . .................................................. </pre>

大家可以看到裙犹,虛線圈起來的地方就是用于跨組件調(diào)用的target和action尽狠,這種方式避免了由BusinessA直接提供組件間調(diào)用會增加的復(fù)雜度衔憨,而且任何組件如果想要對外提供調(diào)用服務(wù),直接掛上target和action就可以了袄膏,業(yè)務(wù)本身在大多數(shù)場景下去進行組件化改造時践图,是基本不用動的。

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

這里我們需要針對術(shù)語做一個理解上的統(tǒng)一:

復(fù)雜參數(shù)是指由普通類型的數(shù)據(jù)組成的多層級參數(shù)码党。在本文中,我們定義只要是能夠被json解析的類型就都是普通類型斥黑,包括NSNumber, NSString, NSArray僻他, NSDictionary萧朝,以及相關(guān)衍生類型,比如來自系統(tǒng)的NSMutableArray或者你自己定義的都算鹿蜀。

總結(jié)一下就是:在本文討論的場景中箕慧,復(fù)雜參數(shù)的定義是由普通類型組成的具有復(fù)雜結(jié)構(gòu)的參數(shù)。普通類型的定義就是指能夠被json解析的類型耻姥。

非常規(guī)參數(shù)是指由普通類型以外的類型組成的參數(shù)销钝,例如UIImage等這些不能夠被json解析的類型。然后這些類型組成的參數(shù)在文中就被定義為非常規(guī)參數(shù)琐簇。

總結(jié)一下就是:非常規(guī)參數(shù)是包含非常規(guī)類型的參數(shù)蒸健。非常規(guī)類型的定義就是不能被json解析的類型都叫非常規(guī)類型。

邊界情況:

  • 假設(shè)多層級參數(shù)中有存在任何一個內(nèi)容是非常規(guī)參數(shù)婉商,本文中這種參數(shù)就也被認為是非常規(guī)參數(shù)似忧。

  • 如果某個類型當前不能夠被json解析,但通過某種轉(zhuǎn)化方式能夠轉(zhuǎn)化成json丈秩,那么這種類型在場景上下文中盯捌,我們也稱為普通類型。

舉個例子就是通過json描述的自定義view蘑秽。如果這個view能夠通過某個組件被轉(zhuǎn)化成json饺著,那么即使這個view本身并不是普通類型,在具有轉(zhuǎn)化器的上下文場景中肠牲,我們依舊認為它是普通類型幼衰。

  • 如果上下文場景中沒有轉(zhuǎn)化器,這個view就是非常規(guī)類型了缀雳。

  • 假設(shè)轉(zhuǎn)化出的json不能夠被還原成view渡嚣,比如組件A有轉(zhuǎn)化器,組件B中沒有轉(zhuǎn)化器,因此在組件間調(diào)用過程中json在B組件里不能被還原成view识椰。在這種調(diào)用方向中绝葡,只要調(diào)用者能將非常規(guī)類型轉(zhuǎn)化成json的,我們就依然認為這個view是普通類型腹鹉。如果調(diào)用者是組件A藏畅,轉(zhuǎn)化器在組件B中,A傳遞view參數(shù)時是沒辦法轉(zhuǎn)化成json的种蘸,那么這個view就被認為是非常規(guī)類型墓赴,哪怕它在組件B中能夠被轉(zhuǎn)化成json。

然后我來解釋一下為什么應(yīng)該由本地組件間調(diào)用來支持遠程應(yīng)用調(diào)用:

在遠程App調(diào)用時航瞭,遠程App是不可能通過URL來提供非常規(guī)參數(shù)的诫硕,最多只能以json string的方式經(jīng)過URLEncode之后再通過GET來提供復(fù)雜參數(shù),然后再在本地組件中解析json刊侯,最終完成調(diào)用章办。在組件間調(diào)用時,通過performTarget:action:params:是能夠提供非常規(guī)參數(shù)的滨彻,于是我們可以知道藕届,遠程App調(diào)用時的上下文環(huán)境以及功能是本地組件間調(diào)用時上下文環(huán)境以及功能的子集

因此這個邏輯注定了必須由本地組件間調(diào)用來為遠程App調(diào)用來提供服務(wù)亭饵,只有符合這個邏輯的設(shè)計思路才是正確的組件化方案的設(shè)計思路休偶,其他跟這個不一致的思路一定就是錯的。因為邏輯上子集為父集提供服務(wù)說不通辜羊,所以強行這么做的話踏兜,用一個成語來總結(jié)就叫做倒行逆施。

另外八秃,遠程App調(diào)用和本地組件間調(diào)用必須要拆分開碱妆,遠程App調(diào)用只能走CTMediator提供的專用遠程的方法,本地組件間調(diào)用只能走CTMediator提供的專用本地的方法昔驱,兩者不能通過同一個接口來調(diào)用疹尾。

這里有兩個原因:

  • 遠程App調(diào)用處理入?yún)⒌倪^程比本地多了一個URL解析的過程,這是遠程App調(diào)用特有的過程骤肛。這一點我前面說過纳本,這里我就不細說了。

  • 架構(gòu)師沒有充要條件條件可以認為遠程App調(diào)用對于無響應(yīng)請求的處理方式和本地組件間調(diào)用無響應(yīng)請求的處理方式在未來產(chǎn)品的演進過程中是一致的

在遠程App調(diào)用中腋颠,用戶通過url進入app饮醇,當app無法為這個url提供服務(wù)時,常見的辦法是展示一個所謂的404界面秕豫,告訴用戶"當前沒有相對應(yīng)的內(nèi)容,不過你可以在app里別的地方再逛逛"。這個場景多見于用戶使用的App版本不一致混移。比如有一個URL只有1.1版本的app能完整響應(yīng)祠墅,1.0版本的app雖然能被喚起,但是無法完成整個響應(yīng)過程歌径,那么1.0的app就要展示一個404了毁嗦。

在組件間調(diào)用中,如果遇到了無法響應(yīng)的請求回铛,就要分兩種場景考慮了狗准。

場景1

如果這種無法響應(yīng)的請求發(fā)生場景是在開發(fā)過程中,比如兩個組件同時在開發(fā)茵肃,組件A調(diào)用組件B時腔长,組件B還處于舊版本沒有發(fā)布新版本,因此響應(yīng)不了验残,那么這時候的處理方式可以相對隨意捞附,只要能體現(xiàn)B模塊是舊版本就行了,最后在RC階段統(tǒng)測時是一定能夠發(fā)現(xiàn)的您没,只要App沒發(fā)版鸟召,怎么處理都來得及。

場景2

如果這種無法響應(yīng)的請求發(fā)生場景是在已發(fā)布的App中氨鹏,有可能展示個404就結(jié)束了欧募,那這就跟遠程App調(diào)用時的404處理場景一樣。但也有可能需要為此做一些額外的事情仆抵,有可能因為做了額外的事情跟继,就不展示404了,展示別的頁面了肢础,這一切取決于產(chǎn)品經(jīng)理还栓。

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

我舉一個例子:當用戶在1.0版本時收藏了一個東西传轰,然后用戶升級App到1.1版本剩盒。1.0版本的收藏項目在本地持久層存入的數(shù)據(jù)有可能是會跟1.1版本收藏時存入的數(shù)據(jù)是不一致的。此時用戶在1.1版本的app中對1.0版本收藏的東西做了一些操作慨蛙,觸發(fā)了本地組件間調(diào)用辽聊,這個本地間調(diào)用又與收藏項目本身的數(shù)據(jù)相關(guān),那么這時這個調(diào)用就是有可能變成無響應(yīng)調(diào)用期贫,此時的處理方式就不見得跟以前一樣展示個404頁面就結(jié)束了跟匆,因為用戶已經(jīng)看到了收藏了的東西,結(jié)果你還告訴他找不到通砍,用戶立刻懵逼玛臂。烤蜕。。這時候的處理方式就會用很多種迹冤,至于產(chǎn)品經(jīng)理會選擇哪種讽营,你作為架構(gòu)師是沒有辦法預(yù)測的。如果產(chǎn)品經(jīng)理提的需求落實到架構(gòu)上泡徙,對調(diào)用入口產(chǎn)生要求然而你的架構(gòu)又沒有拆分調(diào)用入口橱鹏,對于你的選擇就只有兩個:要么打回產(chǎn)品需求,要么加個班去拆分調(diào)用入口堪藐。

當然莉兰,架構(gòu)師可以選擇打回產(chǎn)品經(jīng)理的需求,最終挑選一個自己的架構(gòu)能夠承載的需求礁竞。但是糖荒,如果這種是因為你早期設(shè)計架構(gòu)時挖的坑而打回的產(chǎn)品需求,你不覺得丟臉么苏章?

鑒于遠程app調(diào)用和本地組件間調(diào)用下的無響應(yīng)請求處理方式不同寂嘉,以及未來不可知的產(chǎn)品演進,拆分遠程app調(diào)用入口和本地組件間調(diào)用入口是功在當代利在千秋的事情枫绅。


組件化方案中的去model設(shè)計

組件間調(diào)用時泉孩,是需要針對參數(shù)做去model化的。如果組件間調(diào)用不對參數(shù)做去model化的設(shè)計并淋,就會導(dǎo)致業(yè)務(wù)形式上被組件化了寓搬,實質(zhì)上依然沒有被獨立

假設(shè)模塊A和模塊B之間采用model化的方案去調(diào)用县耽,那么調(diào)用方法時傳遞的參數(shù)就會是一個對象句喷。

如果對象不是一個面向接口的通用對象,那么mediator的參數(shù)處理就會非常復(fù)雜兔毙,因為要區(qū)分不同的對象類型唾琼。如果mediator不處理參數(shù),直接將對象以范型的方式轉(zhuǎn)交給模塊B澎剥,那么模塊B必然要包含對象類型的聲明锡溯。假設(shè)對象聲明放在模塊A,那么B和A之間的組件化只是個形式主義哑姚。如果對象類型聲明放在mediator祭饭,那么對于B而言,就不得不依賴mediator叙量。但是倡蝙,大家可以從上面的架構(gòu)圖中看到,對于響應(yīng)請求的模塊而言绞佩,依賴mediator并不是必要條件寺鸥,因此這種依賴是完全不需要的猪钮,這種依賴的存在對于架構(gòu)整體而言,是一種污染胆建。

如果參數(shù)是一個面向接口的對象躬贡,那么mediator對于這種參數(shù)的處理其實就沒必要了,更多的是直接轉(zhuǎn)給響應(yīng)方的模塊眼坏。而且接口的定義就不可能放在發(fā)起方的模塊中了,只能放在mediator中酸些。響應(yīng)方如果要完成響應(yīng)宰译,就也必須要依賴mediator,然而前面我已經(jīng)說過魄懂,響應(yīng)方對于mediator的依賴是不必要的沿侈,因此參數(shù)其實也并不適合以面向接口的對象的方式去傳遞。

因此市栗,使用對象化的參數(shù)無論是否面向接口缀拭,帶來的結(jié)果就是業(yè)務(wù)模塊形式上是被組件化了,但實質(zhì)上依然沒有被獨立填帽。

在這種跨模塊場景中蛛淋,參數(shù)最好還是以去model化的方式去傳遞,在iOS的開發(fā)中篡腌,就是以字典的方式去傳遞褐荷。這樣就能夠做到只有調(diào)用方依賴mediator,而響應(yīng)方不需要依賴mediator嘹悼。然而在去model化的實踐中叛甫,由于這種方式自由度太大,我們至少需要保證調(diào)用方生成的參數(shù)能夠被響應(yīng)方理解杨伙,然而在組件化場景中其监,限制去model化方案的自由度的手段,相比于網(wǎng)絡(luò)層和持久層更加容易得多限匣。

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

在去model的組件化方案中睛约,影響效率的點有兩個:調(diào)用方如何知道接收方需要哪些key的參數(shù)?調(diào)用方如何知道有哪些target可以被調(diào)用哲身?其實后面的那個問題不管是不是去model的方案辩涝,都會遇到。為什么放在一起說勘天,因為我接下來要說的解決方案可以把這兩個問題一起解決怔揩。

解決方案就是使用category

mediator這個repo維護了若干個針對mediator的category捉邢,每一個對應(yīng)一個target,每個category里的方法對應(yīng)了這個target下所有可能的調(diào)用場景商膊,這樣調(diào)用者在包含mediator的時候伏伐,自動獲得了所有可用的target-action,無論是調(diào)用還是參數(shù)傳遞晕拆,都非常方便藐翎。接下來我要解釋一下為什么是category而不是其他:

  • category本身就是一種組合模式,根據(jù)不同的分類提供不同的方法实幕,此時每一個組件就是一個分類吝镣,因此把每個組件可以支持的調(diào)用用category封裝是很合理的。

  • 在category的方法中可以做到參數(shù)的驗證昆庇,在架構(gòu)中對于保證參數(shù)安全是很有必要的末贾。當參數(shù)不對時,category就提供了補救的入口整吆。

  • category可以很輕松地做請求轉(zhuǎn)發(fā)拱撵,如果不采用category,請求轉(zhuǎn)發(fā)邏輯就非常難做了表蝙。

  • category統(tǒng)一了所有的組件間調(diào)用入口拴测,因此無論是在調(diào)試還是源碼閱讀上,都為工程師提供了極大的方便勇哗。

  • 由于category統(tǒng)一了所有的調(diào)用入口昼扛,使得在跨模塊調(diào)用時,對于param的hardcode在整個App中的作用域僅存在于category中欲诺,在這種場景下的hardcode就已經(jīng)變成和調(diào)用宏或者調(diào)用聲明沒有任何區(qū)別了抄谐,因此是可以接受的。

這里是業(yè)務(wù)方使用category調(diào)用時的場景扰法,大家可以看到非常方便蛹含,不用去記URL也不用糾結(jié)到底應(yīng)該傳哪些參數(shù)。

<pre style="box-sizing: border-box; overflow: auto; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 13px; display: block; padding: 9.5px; margin: 0px 0px 10px; line-height: 1.42857; color: rgb(208, 208, 208); word-break: break-all; overflow-wrap: break-word; background: rgb(32, 32, 32); border: 1px solid rgb(204, 204, 204); border-radius: 4px;"> `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);
    }];
}` </pre>

本文對應(yīng)的demo展示了如何使用category來實現(xiàn)去model的組件調(diào)用澡腾。上面的代碼片段也是摘自這個demo沸伏。


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

基于安全考慮

我們需要防止黑客通過URL的方式調(diào)用本屬于native的組件糕珊,比如支付寶的個人財產(chǎn)頁面。如果在調(diào)用層級上沒有區(qū)分好毅糟,沒有做好安全措施红选,黑客就有通過safari查看任何人的個人財產(chǎn)的可能。

安全措施其實有很多姆另,大部分取決于App本身以及產(chǎn)品的要求喇肋。在架構(gòu)層面要做的最基礎(chǔ)的一點就是區(qū)分調(diào)用是來自于遠程App還是本地組件,我在demo中的安全措施是采用給action添加native前綴去做的迹辐,凡是帶有native前綴的就都只允許本地組件調(diào)用苟蹈,如果在url階段發(fā)現(xiàn)調(diào)用了前綴為native的方法,那就可以采取響應(yīng)措施了右核。這也是將遠程app調(diào)用入口和本地組件調(diào)用入口區(qū)分開來的重要原因之一。

當然渺绒,為了確保安全的做法有很多贺喝,但只要拆出遠程調(diào)用和本地調(diào)用,各種做法就都有施展的空間了宗兼。

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

動態(tài)調(diào)度的意思就是躏鱼,今天我可能這個跳轉(zhuǎn)是要展示A頁面,但是明天可能同樣的跳轉(zhuǎn)就要去展示B頁面了殷绍。這個跳轉(zhuǎn)有可能是來自于本地組件間跳轉(zhuǎn)也有可能是來自于遠程app染苛。

做這個事情的切點在本文架構(gòu)中,有很多個:

  1. 以url parse為切點
  2. 以實例化target時為切點
  3. 以category調(diào)度方法為切點
  4. 以target下的action為切點

如果以url parse為切點的話主到,那么這個動態(tài)調(diào)度就只能夠?qū)h程App跳轉(zhuǎn)產(chǎn)生影響茶行,失去了動態(tài)調(diào)度本地跳轉(zhuǎn)的能力,因此是不適合的登钥。

如果以實例化target時為切點的話畔师,就需要在代碼中針對所有target都做一次審查,看是否要被調(diào)度牧牢,這是沒必要的看锉。假設(shè)10個調(diào)用請求中,只有1個要被動態(tài)調(diào)度塔鳍,那么就必須要審查10次伯铣,只有那1次審查通過了,才走動態(tài)調(diào)度轮纫,這是一種相對比較粗暴的方法腔寡。

如果以category調(diào)度方法為切點的話,那動態(tài)調(diào)度就只能影響到本地件組件的跳轉(zhuǎn)蜡感,因為category是只有本地才用的蹬蚁,所以也不適合恃泪。

以target下的action為切點是最適合的,因為動態(tài)調(diào)度在一般場景下都是有范圍的犀斋,大多數(shù)是活動頁需要動態(tài)調(diào)度贝乎,今天這個活動明天那個活動,或者今天活動正在進行明天活動就結(jié)束了叽粹,所以產(chǎn)生動態(tài)調(diào)度的需求览效。我們在可能產(chǎn)生動態(tài)調(diào)度的action中審查當前action是否需要被動態(tài)調(diào)度,在常規(guī)調(diào)度中就沒必要審查了虫几,例如個人主頁的跳轉(zhuǎn)锤灿,商品詳情的跳轉(zhuǎn)等,這樣效率就能比較高辆脸。

大家會發(fā)現(xiàn)但校,如果要做類似這種效率更高的動態(tài)調(diào)度,target-action層被抽象出來就是必不可少的啡氢,然而蘑菇街并沒有抽象出target-action層寓娩,這也是其中的一個問題港华。

當然,如果你的產(chǎn)品要求所有頁面都是存在動態(tài)調(diào)度需求的,那就還是以實例化target時為切點去調(diào)度了饭冬,這樣能做到審查每一次調(diào)度請求蹬癌,從而實現(xiàn)動態(tài)調(diào)度捻浦。

說完了調(diào)度切點秒梅,接下來要說的就是如何完成審查流程。完整的審查流程有幾種瘤睹,我每個都列舉一下:

  1. App啟動時下載調(diào)度列表升敲,或者定期下載調(diào)度列表。然后審查時檢查當前action是否存在要被動態(tài)調(diào)度跳轉(zhuǎn)的action轰传,如果存在冻晤,則跳轉(zhuǎn)到另一個action
  2. 每一次到達新的action時,以action為參數(shù)調(diào)用API獲知是否需要被跳轉(zhuǎn)绸吸,如果需要被跳轉(zhuǎn)鼻弧,則API告知要跳轉(zhuǎn)的action,然后再跳轉(zhuǎn)到API指定的action

這兩種做法其實都可以锦茁,如果產(chǎn)品對即時性的要求比較高攘轩,那么采用第二種方案,如果產(chǎn)品對即時性要求不那么高码俩,第一種方案就可以了度帮。由于本文的方案是沒有URL注冊列表的,因此服務(wù)器只要給出原始target-action和對應(yīng)跳轉(zhuǎn)的target-action就可以了,整個流程不是只有注冊URL列表才能達成的笨篷,而且這種方案比注冊URL列表要更易于維護一些瞳秽。

另外,說采用url rewrite的手段來進行動態(tài)調(diào)度率翅,也不是不可以练俐。但是這里我需要辨析的是,URL的必要性僅僅體現(xiàn)在遠程App調(diào)度中冕臭,是沒必要蔓延到本地組件間調(diào)用的腺晾。這樣,當我們做遠程App的URL路由時(目前的demo沒有提供URL路由功能辜贵,但是提供了URL路由操作的接入點悯蝉,可以根據(jù)業(yè)務(wù)需求插入這個功能),要關(guān)心的事情就能少很多托慨,可以比較干凈鼻由。在這種場景下,單純以URL rewrite的方式其實就與上文提到的以url parse為切點沒有區(qū)別了厚棵。

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

  • 蘑菇街沒有拆分遠程調(diào)用和本地間調(diào)用

不拆分遠程調(diào)用和本地間調(diào)用,就使得后續(xù)很多手段難以實施窟感,這個我在前文中都已經(jīng)有論述了。另外再補充一下歉井,這里的拆分不是針對來源做拆分柿祈。比如通過URL來區(qū)分是遠程App調(diào)用還是本地調(diào)用,這只是區(qū)分了調(diào)用者的來源哩至。

這里說的區(qū)分是指:遠程調(diào)用走遠程調(diào)用路徑躏嚎,也就是openUrl->urlParse->perform->target-action。本地組件間調(diào)用就走本地組件間調(diào)用路徑:perform->target-action菩貌。這兩個是一定要作區(qū)分的卢佣,蘑菇街方案并沒有對此做好區(qū)分。

  • 蘑菇街以遠程調(diào)用的方式為本地間調(diào)用提供服務(wù)

這是本末倒置的做法箭阶,倒行逆施導(dǎo)致的是未來架構(gòu)難以為業(yè)務(wù)發(fā)展提供支撐虚茶。因為前面已經(jīng)論述過,在iOS場景下仇参,遠程調(diào)用的實現(xiàn)是本地調(diào)用實現(xiàn)的子集嘹叫,只有大的為小提供服務(wù),也就是本地調(diào)用為遠程調(diào)用提供服務(wù)诈乒,如果反過來就是倒行逆施了罩扇。

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

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

由于采用遠程調(diào)用的方式執(zhí)行本地調(diào)用喂饥,在前面已經(jīng)論述過兩者功能集的關(guān)系消约,因此這種做法無法滿足傳遞非常規(guī)參數(shù)的需求。而且如果基于這種方式不變的話员帮,復(fù)雜參數(shù)的傳遞也只能依靠經(jīng)過urlencode的json string進行或粮,這種方式非常丑陋,而且也不便于調(diào)試集侯。

  • 蘑菇街必須要在app啟動時注冊URL響應(yīng)者

這個條件在組件化方案中是不必要條件被啼,demo也已經(jīng)證實了這一點。這個不必要的操作會導(dǎo)致不必要的維護成本棠枉,如果單純從只要完成業(yè)務(wù)就好的角度出發(fā)浓体,這倒不是什么大問題。這就看架構(gòu)師對自己是不是要求嚴格了辈讶。

  • 新增組件化的調(diào)用路徑時命浴,蘑菇街的操作相對復(fù)雜

在本文給出的組件化方案中,響應(yīng)者唯一要做的事情就是提供Target和Action贱除,并不需要再做其它的事情生闲。蘑菇街除此之外還要再做很多額外不必要措施,才能保證調(diào)用成功月幌。

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

這種做法使得所有的跨組件調(diào)用請求直接hit到業(yè)務(wù)模塊碍讯,業(yè)務(wù)模塊必然因此變得臃腫難以維護,屬于侵入式架構(gòu)扯躺。應(yīng)該將原本屬于調(diào)用相應(yīng)的部分拿出來放在target-action中捉兴,才能盡可能保證不將無關(guān)代碼侵入到原有業(yè)務(wù)組件中,才能保證業(yè)務(wù)組件未來的遷移和修改不受組件調(diào)用的影響录语,以及降低為項目的組件化實施而帶來的時間成本倍啥。

總結(jié)

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

然而這款方案有一個很小的缺陷在于對param的key的hardcode澎埠,這是為了達到最大限度的解耦和靈活度而做的權(quán)衡虽缕。在我的網(wǎng)絡(luò)層架構(gòu)和持久層架構(gòu)中,都沒有hardcode的場景蒲稳,這也從另一個側(cè)面說明了組件化架構(gòu)的特殊性氮趋。

權(quán)衡時,考慮到這部分hardcode的影響域僅僅存在于mediator的category中江耀。在這種情況下凭峡,hardcode對于調(diào)用者的調(diào)用是完全透明的。對于響應(yīng)者而言决记,處理方式等價于對API返回的參數(shù)的處理方式摧冀,且響應(yīng)者的處理方式也被限制在了Action中

因此這部分的hardcode的存在雖然確實有點不干凈,但是相比于這些不干凈而帶來的其他好處而言索昂,在權(quán)衡時是可以接受的建车,如果不采用hardcode,那勢必就會導(dǎo)致請求響應(yīng)方也需要依賴mediator椒惨,然而這在邏輯上是不必要的缤至。另外,在我的各個項目的實際使用過程中康谆,這部分hardcode是沒有影響的领斥。

另外要談的是,之所以會在組件化方案中出現(xiàn)harcode沃暗,而網(wǎng)絡(luò)層和持久層的去model化都沒有發(fā)生hardcode情況月洛,是因為組件化調(diào)用的所有接受者和調(diào)用者都在同一片上下文里。網(wǎng)絡(luò)層有一方在服務(wù)端孽锥,持久層有一方在數(shù)據(jù)庫嚼黔。再加上設(shè)計時針對hardcode部分的改進手段其實已經(jīng)超出了語言本身的限制。也就是說惜辑,harcode受限于語言本身唬涧。objective-C也好,swift也好盛撑,它們的接口設(shè)計哲學(xué)是存在缺陷的碎节。如果我們假設(shè)在golang的背景下,是完全可以用golang的接口體系去做一個最優(yōu)美的架構(gòu)方案出來的抵卫。不過這已經(jīng)不屬于本文的討論范圍了狮荔,有興趣的同學(xué)可以去了解一下相關(guān)知識。架構(gòu)設(shè)計有時就是這么無奈陌僵。

組件化方案在App業(yè)務(wù)穩(wěn)定,且規(guī)模(業(yè)務(wù)規(guī)模和開發(fā)團隊規(guī)模)增長初期去實施非常重要创坞,它助于將復(fù)雜App分而治之碗短,也有助于多人大型團隊的協(xié)同開發(fā)。但組件化方案不適合在業(yè)務(wù)不穩(wěn)定的情況下過早實施题涨,至少要等產(chǎn)品已經(jīng)經(jīng)過MVP階段時才適合實施組件化偎谁。因為業(yè)務(wù)不穩(wěn)定意味著鏈路不穩(wěn)定,在不穩(wěn)定的鏈路上實施組件化會導(dǎo)致將來主業(yè)務(wù)產(chǎn)生變化時纲堵,全局性模塊調(diào)度和重構(gòu)會變得相對復(fù)雜巡雨。

當決定要實施組件化方案時,對于組件化方案的架構(gòu)設(shè)計優(yōu)劣直接影響到架構(gòu)體系能否長遠地支持未來業(yè)務(wù)的發(fā)展席函,對App的組件化不只是僅僅的拆代碼和跨業(yè)務(wù)調(diào)頁面铐望,還要考慮復(fù)雜和非常規(guī)業(yè)務(wù)參數(shù)參與的調(diào)度,非頁面的跨組件功能調(diào)度,組件調(diào)度安全保障正蛙,組件間解耦督弓,新舊業(yè)務(wù)的調(diào)用接口修改等問題。

蘑菇街的組件化方案只實現(xiàn)了跨業(yè)務(wù)頁面調(diào)用的需求乒验,本質(zhì)上只實現(xiàn)了我在view層架構(gòu)的文章中跨業(yè)務(wù)頁面調(diào)用的內(nèi)容愚隧,這還沒有到成為組件化方案的程度,且蘑菇街的組件化方案距離真正的App組件化的要求還是差了一段距離的锻全,且存在設(shè)計邏輯缺陷狂塘,希望蘑菇街能夠加緊重構(gòu),打造真正的組件化方案鳄厌。

2016-03-14 20:26 補

沒想到limboy如此迅速地發(fā)文回應(yīng)了荞胡。文章地址在這里:蘑菇街 App 的組件化之路 續(xù)。然后我花了一些時間重新看了limboy的第一篇文章部翘。我覺得在本文開頭我對蘑菇街的組件化方案描述過于簡略了硝训,而且我還忽略了原來是有ModuleManager的,所以在這里我重新描述一番。

蘑菇街是以兩種方式來做跨組件操作的

第一種是通過MGJRouterregisterURLPattern:toHandler:進行注冊树酪,將URL和block綁定恐锣。這個方法前面一個參數(shù)傳遞的是URL,例如mgj://detail?id=:id這種纵刘,后面的toHandler:傳遞的是一個^(NSDictionary *routerParameters){// 此處可以做任何事}的block。

當組件執(zhí)行[MGJRouter openURL:@"mgj://detail?id=404"]時荸哟,根據(jù)之前registerURLPattern:toHandler:的信息假哎,找到之前通過toHandler:收集的block,然后將URL中帶的GET參數(shù)鞍历,此處是id=404舵抹,傳入block中執(zhí)行。如果在block中執(zhí)行NSLog(routerParameters)的話劣砍,就會看到@{@"id":@"404"}惧蛹,因此block中的業(yè)務(wù)就能夠得到執(zhí)行。

然后為了業(yè)務(wù)方能夠不生寫URL刑枝,蘑菇街列出了一系列宏或者字符串常量(具體是宏還是字符串我就不是很確定香嗓,沒看過源碼,但limboy文章中有提到通過一個后臺系統(tǒng)生成一個裝滿URL的源碼文件)來表征URL装畅。在openURL時靠娱,無論是遠程應(yīng)用調(diào)用還是本地組件間調(diào)用,只要傳遞的參數(shù)不復(fù)雜掠兄,就都會采用openURL的方式去喚起頁面像云,因為復(fù)雜的參數(shù)和非常規(guī)參數(shù)這種調(diào)用方式就無法支持了锌雀。

缺陷在于:這種注冊的方式其實是不必要的,而且還白白使用URLblock占用了內(nèi)存苫费。另外還有一個問題就是汤锨,即便是簡單參數(shù)的傳遞,如果參數(shù)比較多百框,業(yè)務(wù)工程師不看原始URL字符串是無法知道要傳遞哪些參數(shù)的闲礼。

蘑菇街之所以采用id=:id的方式,我猜是為了怕業(yè)務(wù)工程師傳遞多個參數(shù)順序不同會導(dǎo)致問題铐维,而使用的占位符柬泽。這種做法在持久層生成sql字符串時比較常見。不過這個功能我沒在limboy的文章中看到有寫嫁蛇,不知道實現(xiàn)了沒有锨并。

在本文提供的組件化方案中,因為沒有注冊睬棚,所以就沒有內(nèi)存的問題第煮。因為通過category提供接口調(diào)用,就沒有參數(shù)的問題抑党。對于蘑菇街來說包警,這種做法其實并沒有做到拆分遠程應(yīng)用調(diào)用和本地組件間調(diào)用的目的,而不拆分會導(dǎo)致的問題我在文章中已經(jīng)論述過了底靠,這里就不多說了害晦。


由于前面openURL的方式不能夠傳遞非常規(guī)參數(shù),因此有了第二種注冊方式:新開了一個對象叫做ModuleManager暑中,提供了一個registerClass:forProtocol:的方法壹瘟,在應(yīng)用啟動時,各組件都會有一個專門的ModuleEntry被喚起鳄逾,然后ModuleEntry@protocolClass進行配對稻轨。因此ModuleManager中就有了一個字典來記錄這個配對。

當有涉及非常規(guī)參數(shù)的調(diào)用時雕凹,業(yè)務(wù)方就不會去使用[MGJRouter openURL:@"mgj://detail?id=404"]的方案了殴俱,轉(zhuǎn)而采用ModuleManagerclassForProtocol:方法。業(yè)務(wù)傳入一個@protocolModuleManager请琳,然后ModuleManager通過之前注冊過的字典查找到對應(yīng)的Class返回給業(yè)務(wù)方粱挡,然后業(yè)務(wù)方再自己執(zhí)行allocinit方法得到一個符合剛才傳入@protocol的對象赠幕,然后再執(zhí)行相應(yīng)的邏輯俄精。

這里的ModuleManager其實跟之前的MGJRouter一樣,是沒有任何必要去注冊協(xié)議和類名的榕堰。而且無論是服務(wù)提供者調(diào)用registerClass:forProtocol:也好竖慧,服務(wù)的調(diào)用者調(diào)用classForProtocol:嫌套,都必須依賴于同一個protocol。蘑菇街把所有的protocol放入了一個publicProtocol.h的文件中圾旨,因此調(diào)用方和響應(yīng)方都必須依賴于同一個文件踱讨。這個我在文章中也論述過:響應(yīng)方在提供服務(wù)的時候,是不需要依賴任何人的砍的。


所以針對蘑菇街的這篇文章我是這么回應(yīng)的:

  • 蘑菇街所謂分開了遠程應(yīng)用調(diào)用和本地組件調(diào)用是不成立的痹筛,蘑菇街分開的只是普通參數(shù)調(diào)用非常規(guī)參數(shù)調(diào)用。不去區(qū)分遠程應(yīng)用調(diào)用和本地組件間調(diào)用的缺陷我在文中已經(jīng)論述過了廓鞠,這里不多說帚稠。

  • 蘑菇街確實不只有openURL方式,還提供了ModuleManager方式床佳,然而所謂的我們其實是分為「組件間調(diào)用」和「頁面間跳轉(zhuǎn)」兩個維度滋早,只要 app 響應(yīng)某個 URL,無論是 app 內(nèi)還是 app 外都可以砌们,而「組件間」調(diào)用走的完全是另一條路杆麸,所以也不會有安全上的問題。其實也是不成立的浪感,因為openURL方式也出現(xiàn)在了本地組件間調(diào)用中昔头,這在他第一篇文章里的組件間通信小節(jié)中就已經(jīng)說了采用openURL方式調(diào)用了,這是有可能產(chǎn)生安全問題的篮撑。而且這段話也承認了openURL方式被用于本地組件間調(diào)用减细,又印證了我剛才說的第一點。

  • 根據(jù)上面兩點赢笨,蘑菇街在openURL場景下未蝌,還是出現(xiàn)了以遠程調(diào)用的方式為本地間調(diào)用提供服務(wù)的問題,這個問題我也已經(jīng)在文中論述過了茧妒。

  • 蘑菇街在本地間調(diào)用同時采用了openURL方案和protocol - class方案萧吠,所以其實之前我指出蘑菇街本地間調(diào)用不能傳遞非常規(guī)參數(shù)和復(fù)雜參數(shù)是不對的,應(yīng)該是蘑菇街在本地間調(diào)用時如果是普通參數(shù)桐筏,那就采用openURL纸型,如果是非常規(guī)參數(shù),那就采用protocol - class了梅忌,這個做法對于本地間調(diào)用的管理和維護狰腌,顯而易見是不利的。牧氮。琼腔。

  • limboy說必須要在 app 啟動時注冊 URL 響應(yīng)者這步不可避免,但沒有說原因踱葛。我的demo已經(jīng)證實了注冊是不必要的丹莲,所以我想聽聽limboy如何解釋原因光坝。

  • 你的架構(gòu)圖畫錯了

mgj

按照你的方案來看,紅圈的地方是不可能沒有依賴的甥材。盯另。。

另外洲赵,limboy也對本文方案提出了一些看法:

認為category在某種意義上也是一個注冊過程鸳惯。

蘑菇街的注冊和我這里的category其實是兩回事,而且我無論如何也無法理解把category和注冊URL等價聯(lián)系的邏輯??

一個很簡單的事實就可以證明兩者完全不等價了:我的方案如果沒有category叠萍,照樣可以跑悲敷,就是業(yè)務(wù)方調(diào)用丑陋一點。蘑菇街如果不注冊URL俭令,整個流程就跑不起來了~

認為openURL的好處是可以更少地關(guān)心業(yè)務(wù)邏輯后德,本文方案的好處是可以很方便地完成參數(shù)傳遞。

我沒覺得本文方案關(guān)心的業(yè)務(wù)邏輯比openURL更多抄腔,因為兩者比較起來瓢湃,都是傳參數(shù)發(fā)調(diào)用請求,在關(guān)心業(yè)務(wù)邏輯的條件下赫蛇,兩者完全一樣绵患。唯一的不同就是,我能傳非常規(guī)參數(shù)而openURL不能悟耘。本文方案的整個過程中落蝙,在調(diào)用者這一方是完全沒有涉及到任何屬于響應(yīng)者的業(yè)務(wù)邏輯的。

認為protocol/URL注冊將target-action抽象出調(diào)用接口是等價的

這其實只是效果等價了暂幼,兩者真正的區(qū)別在于:protocol對業(yè)務(wù)產(chǎn)生了侵入筏勒,且不符合黑盒模型。

  • 我來解釋一下protocol侵入業(yè)務(wù)的原因

由于業(yè)務(wù)中的某個對象需要被調(diào)用旺嬉,因此必須要符合某個可被調(diào)用的protocol管行,然而這個protocol又不存在于當前業(yè)務(wù)領(lǐng)域,于是當前業(yè)務(wù)就不得不依賴publicProtocol邪媳。這對于將來的業(yè)務(wù)遷移是有非常大的影響的捐顷。

  • 另外再解釋一下為什么不符合黑盒模型

蘑菇街的protocol方式使對象要在調(diào)用者處使用,由于調(diào)用者并不包含對象原本所處的業(yè)務(wù)領(lǐng)域雨效,當完成任務(wù)需要多個這樣的對象的時候迅涮,就需要多次通過protocol獲得class來實例化多個對象,最終才能完成需求徽龟。

但是target-action模式保證了在執(zhí)行組件間調(diào)用的響應(yīng)時叮姑,執(zhí)行的上下文處于響應(yīng)者環(huán)境中,這跟蘑菇街的protocol方案相比就是最大的差別顿肺。因為從黑盒理論上講戏溺,調(diào)用者只管發(fā)起請求,請求的執(zhí)行應(yīng)該由響應(yīng)者來負責屠尊,因此執(zhí)行邏輯必須存在于響應(yīng)者的上下文內(nèi)旷祸,而不能存在于調(diào)用者的上下文內(nèi)。

舉個具體一點的例子就是讼昆,當你發(fā)起了一個網(wǎng)頁請求托享,后端取好數(shù)據(jù)渲染好頁面,無論獲取數(shù)據(jù)涉及多少渠道浸赫,獲取數(shù)據(jù)的邏輯都在服務(wù)端完成闰围,然后再返回給瀏覽器展示。這個是正確的做法既峡,target-action模式也是這么做的羡榴。

但是蘑菇街的方案就變成了這樣:你發(fā)起了一個網(wǎng)絡(luò)請求,后端返回的不是數(shù)據(jù)运敢,返回的竟然是一個數(shù)據(jù)獲取對象(DAO)校仑,然后你再通過DAO去取數(shù)據(jù),去渲染頁面传惠,如果渲染頁面的過程涉及多個DAO迄沫,那么你還要再發(fā)起更多請求,拿到的還是DAO卦方,然后再拿這個DAO去獲取數(shù)據(jù)羊瘩,然后渲染頁面。這是一種非常詭異的做法盼砍。尘吗。。

如果說這么做是為了應(yīng)對執(zhí)行業(yè)務(wù)的過程中,需要根據(jù)中間階段的返回值來決定接下來的邏輯走向的話渠脉,那也應(yīng)該是多次調(diào)用獲得數(shù)據(jù)珊膜,然后決定接下來的業(yè)務(wù)走向,而不是每次拿到的都是DAO啊侧戴。。跌宛。使用target-action方式來應(yīng)對這種場景其實也很自然啊~

所以綜上所述酗宋,蘑菇街的方案是存在很大問題的,希望蘑菇街繼續(xù)改正

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末疆拘,一起剝皮案震驚了整個濱河市蜕猫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哎迄,老刑警劉巖回右,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隆圆,死亡現(xiàn)場離奇詭異,居然都是意外死亡翔烁,警方通過查閱死者的電腦和手機渺氧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蹬屹,“玉大人侣背,你說我怎么就攤上這事】” “怎么了贩耐?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長厦取。 經(jīng)常有香客問我潮太,道長,這世上最難降的妖魔是什么虾攻? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任消别,我火速辦了婚禮,結(jié)果婚禮上台谢,老公的妹妹穿的比我還像新娘寻狂。我一直安慰自己,他們只是感情好朋沮,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布蛇券。 她就那樣靜靜地躺著,像睡著了一般樊拓。 火紅的嫁衣襯著肌膚如雪纠亚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天筋夏,我揣著相機與錄音蒂胞,去河邊找鬼。 笑死条篷,一個胖子當著我的面吹牛骗随,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播赴叹,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼鸿染,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了乞巧?” 一聲冷哼從身側(cè)響起涨椒,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蚕冬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體免猾,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年囤热,在試婚紗的時候發(fā)現(xiàn)自己被綠了猎提。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡赢乓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出石窑,到底是詐尸還是另有隱情牌芋,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布松逊,位于F島的核電站躺屁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏经宏。R本人自食惡果不足惜犀暑,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望烁兰。 院中可真熱鬧耐亏,春花似錦、人聲如沸沪斟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽主之。三九已至择吊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間槽奕,已是汗流浹背几睛。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留粤攒,地道東北人所森。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像夯接,于是被迫代替她去往敵國和親必峰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

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