現(xiàn)在很多應(yīng)用都在內(nèi)部跳轉(zhuǎn)中使用了路由系統(tǒng),這種從web搬過(guò)來(lái)的東西混巧。但是功能卻并不完善姜挺,就像比較有名的JLRouter和MGJRouter盐类。這里就來(lái)聊聊更加完善的系統(tǒng)的幾個(gè)部分晰绎。
前言
現(xiàn)在很多的應(yīng)用都在一個(gè)非常大的量級(jí)了锭吨,已經(jīng)不可能由幾個(gè)人的小團(tuán)隊(duì)來(lái)開(kāi)發(fā)了,那么模塊化就非常必要了寒匙。相比很多人說(shuō)的組件化(Component),我覺(jué)得模塊化(Module)更為貼切一點(diǎn)。
說(shuō)到模塊化锄弱,那么模塊間的通信就非常重要了考蕾,如果僅僅是通過(guò)api來(lái)做通信,就不夠靈活会宪,而且可能會(huì)增加了集成成本肖卧。一旦涉及到接口變更就需要修改多個(gè)模塊。這樣路由系統(tǒng)便是頁(yè)面間跳轉(zhuǎn)的一個(gè)非常重要模塊掸鹅,可以說(shuō)他承載了整個(gè)最核心的部分塞帐。一個(gè)好的,完善的路由系統(tǒng)可以讓一個(gè)系統(tǒng)更加穩(wěn)定和開(kāi)發(fā)簡(jiǎn)單巍沙。而一個(gè)功能欠缺的路由系統(tǒng)則有可能將整個(gè)應(yīng)用帶入一個(gè)泥潭葵姥。
目前比較有名的幾個(gè)實(shí)現(xiàn),都非常簡(jiǎn)單句携,而且非常好用榔幸。但這是對(duì)一個(gè)整體的應(yīng)用來(lái)說(shuō)的,如果是一個(gè)多團(tuán)隊(duì)開(kāi)發(fā)的應(yīng)用矮嫉,就有部分缺陷了削咆。所以為了應(yīng)對(duì)這種缺陷,蘑菇街和京東都開(kāi)發(fā)了一個(gè)管理平臺(tái)蠢笋,來(lái)專門管理這些路由系統(tǒng)拨齐,解決這種缺陷。
這些路由實(shí)現(xiàn)在目前已經(jīng)基本滿足需求了昨寞,但是在未來(lái)可能會(huì)進(jìn)行的模塊拆分瞻惋,則需要功能更加強(qiáng)大的路由系統(tǒng)。所以我提出一種更加完備的路由系統(tǒng)编矾∈焓罚可能這里有過(guò)度設(shè)計(jì)的成分,但是作為未來(lái)的擴(kuò)展性窄俏,我覺(jué)得還是有必要的蹂匹。
完備路由
既然我們是從web那里抄過(guò)來(lái)的想法,那么為什么不抄的完整一些呢凹蜈?
回溯 history
目前的幾個(gè)路由系統(tǒng)都沒(méi)有提供這個(gè)功能限寞,而這個(gè)功能我覺(jué)的是非常非常重要的一個(gè)功能。當(dāng)我們需要判斷上個(gè)頁(yè)面是什么頁(yè)面的時(shí)候仰坦,只能通過(guò)判斷controller隊(duì)列中的類型履植!這完全違背了黑箱這種原則。
同時(shí)我們可能需要回退多個(gè)頁(yè)面或者回退到某個(gè)頁(yè)面的時(shí)候悄晃,沒(méi)有歷史記錄的支持玫霎,在合作開(kāi)發(fā)的時(shí)候絕對(duì)是個(gè)災(zāi)難凿滤。
history給我們的功能好處也是非常多的。除了上述幾個(gè)場(chǎng)景中特別需要以外庶近,對(duì)我們的debug以及分析也是非常有幫助的翁脆。甚至我們可以記錄和導(dǎo)出history來(lái)查看用戶的操作流程,雖然不能代表所有的動(dòng)作鼻种,但也可以展示用戶的一系列行為反番,或者在crash分析中自動(dòng)帶上該信息。
自定義進(jìn)入退出
我們的每個(gè)頁(yè)面都不是固定的push
的叉钥,也可能是present
的罢缸,所以相應(yīng)的返回也是不一樣的,然而目前幾個(gè)路由系統(tǒng)都只有進(jìn)入的方式投队,并沒(méi)有退出的方式枫疆。那么只能交給人來(lái)處理這種事情,當(dāng)某個(gè)模塊成為黑箱的時(shí)候蛾洛,外部又怎么去判斷呢养铸?
所以進(jìn)入(push
)和回退(pop
)操作都需要有對(duì)應(yīng)的行為,這就承接了上一個(gè)history的必要性了轧膘。這樣我們同樣是進(jìn)入(push
)和回退(pop
)钞螟,當(dāng)行為是present
時(shí),就對(duì)應(yīng)為present
和dismiss
谎碍。
甚至在內(nèi)嵌的網(wǎng)頁(yè)中也可以接入native的路由系統(tǒng)鳞滨,那么這樣的push
和pop
就對(duì)應(yīng)了網(wǎng)頁(yè)的跳轉(zhuǎn)和返回了。同時(shí)這樣也屏蔽了模塊內(nèi)部的具體行為蟆淀,模塊修改展示方式也不會(huì)影響外部的處理邏輯拯啦。更好的形成一個(gè)黑盒模塊。
子路由
這個(gè)在web中是不存在的熔任,但是在native的app中我覺(jué)得有必要褒链。每個(gè)模塊,或者內(nèi)嵌的網(wǎng)頁(yè)都應(yīng)該是個(gè)子路由系統(tǒng)疑苔。
為什么需要子路由系統(tǒng)呢甫匹?這里舉一個(gè)例子:
Root --[push]-> VC
|
[present]
\
--> VC2 --[push]-> VC3 -->...
當(dāng)我們需要去完成一個(gè)系列的完整動(dòng)作的時(shí)候,往往會(huì)present出一個(gè)新的navigation來(lái)處理這一系列動(dòng)作惦费,而這些行為在任意一個(gè)節(jié)點(diǎn)都可能會(huì)返回兵迅,同時(shí)又可能在任意的一個(gè)地方present出來(lái)。那么如果要滿足這種情景又需要人為的去處理很多邏輯薪贫,這就埋下了隱患恍箭。
如果我們把present出來(lái)的這一系列行為定義為子路由,那么如果需要返回時(shí)瞧省,只需要退出子路由中所有的history就可以了扯夭。
黑盒路由
如果項(xiàng)目變大以后鳍贾,可能就會(huì)存在上百個(gè)路由,那么這些路由中實(shí)際有效的(其他模塊可用交洗,或者公開(kāi)的)路由又有多少呢贾漏?我覺(jué)得應(yīng)該會(huì)很少吧,因?yàn)橥獠窟M(jìn)入一個(gè)其他模塊的入口基本是固定的藕筋,所以我們?yōu)槭裁葱枰┞哆@么多不必要的路由給外部呢。蘑菇街和京東為了管理這些路由干脆搞了一個(gè)管理系統(tǒng)梳码,來(lái)防止路由沖突隐圾。那么為什么我們不讓一個(gè)模塊內(nèi)的路由成為一個(gè)黑箱呢,只暴露外部需要的路由掰茶,而其他路由都經(jīng)過(guò)保護(hù)不能隨意訪問(wèn)暇藏。這樣也防止外部訪問(wèn)不該訪問(wèn)的功能。
這個(gè)功能是承接子路由的實(shí)現(xiàn)濒蒋,我們保持每個(gè)子路由內(nèi)部的黑盒盐碱,可以減少我們的錯(cuò)誤。
保持子路由的黑盒性的一個(gè)簡(jiǎn)單做法就是base url沪伙。依照restful來(lái)設(shè)計(jì)路由瓮顽,不同子路由系統(tǒng)給與一個(gè)base url,比如登錄模塊的路由可以增加base url /auth
围橡。那么內(nèi)部和外部的行為可以概括為:
// 外部
push("/auth")
push("/auth/password")
// 內(nèi)部
push("~")
push("~/phone")
push("~/verify")
push("~/password")
// 退出整個(gè)模塊
pop("~")
重定向
有些頁(yè)面在某些條件下是不能進(jìn)入的暖混,這時(shí)候需要重定向去特定模塊來(lái)完成這個(gè)條件,完成后再次進(jìn)入該模塊翁授。所以我們需要重定向的功能拣播,否則就會(huì)增加很多依賴項(xiàng),導(dǎo)致不能真正的模塊分離收擦。
比如某個(gè)功能需要登錄模塊贮配,在我們沒(méi)有集成登錄模塊的時(shí)候就難以完成功能,而集成了登錄模塊又會(huì)導(dǎo)致模塊化僅僅只是個(gè)表面上的分離了塞赂,可能在真正開(kāi)發(fā)的時(shí)候還是把一大堆其他模塊給搞進(jìn)工程泪勒,那么這樣的模塊化有什么意義?如果擁有重定向就可以偽造一個(gè)重定向减途,讓他自動(dòng)放回需要的結(jié)果就可以了酣藻。這樣就可以排除其他模塊的干擾了和依賴了。
路徑依賴
這個(gè)其實(shí)時(shí)承接上個(gè)需求的鳍置,可以相當(dāng)于一個(gè)語(yǔ)法糖辽剧。
按照上個(gè)場(chǎng)景,我們?nèi)绾稳プ?cè)一個(gè)需要重定向的路由呢税产。那么我們可以引入一個(gè)路由依賴的功能怕轿,可以標(biāo)記每個(gè)路由的依賴選項(xiàng)偷崩。比如登錄的@requireLogin
。
這算是一個(gè)擴(kuò)展的功能吧撞羽。
中間件
目前主流的幾個(gè)路由還有一個(gè)非常欠缺的功能就是中間件阐斜。我們無(wú)法去從路由系統(tǒng)內(nèi)部知道我們經(jīng)過(guò)了哪些操作,是否需要過(guò)濾某些操作诀紊。
這個(gè)話題其實(shí)也關(guān)系著黑盒路由和重定向的問(wèn)題谒出。依照服務(wù)端的中間件設(shè)計(jì)方式,我們應(yīng)該需要為路由系統(tǒng)留出控制以及debug的入口邻奠。
擁有了中間件我們可以做什么笤喳?
- debug,打印所有的入口和出口碌宴,完善我們的日志
- 分模塊開(kāi)發(fā)的時(shí)候杀狡,可以mock其他有依賴的模塊
- 監(jiān)視,是否調(diào)用了非法的接口或者使用了非法的參數(shù)
個(gè)人覺(jué)得中間件是一個(gè)系統(tǒng)所必須的功能贰镣,可以提供外部實(shí)現(xiàn)更多的功能和更靈活的控制呜象。
熱插件
這是一個(gè)額外的話題。由于我們的路由注冊(cè)基本上是在+load
方法里面做的碑隆,所以當(dāng)我們的路由越來(lái)越多的時(shí)候恭陡,啟動(dòng)性能也會(huì)越來(lái)越差,那么提供熱插拔的功能是最好的干跛。同時(shí)熱插拔功能也相對(duì)應(yīng)的提高了路由的黑盒性子姜。
做成熱插件的形式還有一些好處。比如abtest的時(shí)候楼入,可以加載不同的插件來(lái)進(jìn)入不同的頁(yè)面哥捕。又或者在某個(gè)頁(yè)面出了線上問(wèn)題,需要降級(jí)為web來(lái)實(shí)現(xiàn)嘉熊,也可以通過(guò)替換插件來(lái)替換不同的頁(yè)面遥赚。
這是一個(gè)錦上添花的功能,所以我把他列到了最后阐肤。
總結(jié)
以上說(shuō)的幾個(gè)點(diǎn)可能給我們的編碼增加一些復(fù)雜度凫佛,而且還需要去理解這些概念才能很好的運(yùn)用。但這里的規(guī)劃完全是以模塊化為前提的孕惜,并且獨(dú)立為一個(gè)系統(tǒng)愧薛,不去依賴UIKit
。所以從長(zhǎng)遠(yuǎn)來(lái)看這樣的實(shí)現(xiàn)是絕對(duì)有好處的衫画,如果有時(shí)間可以把這個(gè)想法實(shí)現(xiàn)出來(lái)毫炉。