帶你一步步構(gòu)建iOS路由

接上一篇移動端路由層設(shè)計,這一篇是實戰(zhàn)篇丹鸿,手把手的帶你編寫一個簡單的路由組件贪染。有朋友說很多人都收藏以后就再也沒看過缓呛,其實這屬于時間管理問題,在你忙碌的工作和生活的時候杭隙,有時候需要你稍微停頓一下哟绊,思考一下,例如痰憎,你可以把本篇文章收藏以后再在iPhone的提醒事項里加入到一個閱讀清單里票髓,不用設(shè)置提醒,只需要在你閑的時候抽出一兩個小時铣耘,看一下洽沟。想象一下你自己動手從發(fā)現(xiàn)問題到解決問題再到做出一個解決問題的組件的過程給你帶來的成就感和獲取的進階經(jīng)驗,再稍微改變一下你對每天需要處理的繁雜事物的管理方式蜗细,也許你的生活和工作就會豁然開朗裆操。

這個路由究竟是什么鬼?能解決什么問題炉媒?

舉一些場景來看看

場景1:一個App項目中團隊人員比較多踪区,不同的人負(fù)責(zé)不同的模塊開發(fā),有的人直接使用資源文件設(shè)計的吊骤,有的人用代碼直接寫的缎岗,有的人負(fù)責(zé)登錄,有的人負(fù)責(zé)訂單白粉,突然有一天搞訂單的開發(fā)A找搞登錄的開發(fā)B說要調(diào)一下登錄密强,登錄成功以后你要再回調(diào)下我寫的模塊的方法告訴我成功登錄茅郎,我要刷新一下訂單頁面,B傻傻的就答應(yīng)了或渤,找B的人C系冗、D、F....越來越多薪鹦,B負(fù)責(zé)的代碼越寫越多掌敬,同時A也不怎么開心,因為A發(fā)現(xiàn)調(diào)B寫的登錄要通過類實例化函數(shù)獲取模塊池磁,調(diào)C寫的支付使用工廠方法奔害,調(diào)D寫的計算器組件又是另外一種寫法,結(jié)果A自己的代碼也越來越丑地熄。

場景2:一個App里面有很多內(nèi)嵌的H5頁面华临,纏品A對猿B說,我們的活動頁面要調(diào)用一下我們的訂單頁面端考,用戶如果下了一個訂單成功以后H5要能夠拿到反饋有歡迎語雅潭,猿B和H5的開發(fā)猿C經(jīng)過很久很久的討論,確定了H5如果調(diào)用App的訂單頁面却特,參數(shù)怎么傳扶供,訂單提交以后怎么再調(diào)H5的接口,參數(shù)怎么定義裂明,各自把代碼寫到各自的項目里椿浓,沒過多久纏品A說另外的H5要調(diào)用原生的界面,怎么怎么個流程闽晦,推送點擊要調(diào)用原生的某個頁面扳碍,點完要反饋給后臺統(tǒng)計,兄弟App要跳轉(zhuǎn)到我們的App某個頁面跳轉(zhuǎn)完成某個動作以后要再跳轉(zhuǎn)回去......猿B每每接到這樣的需求就緊緊握住自己中箭的膝蓋仙蛉,收拾了一下寫的那么多代碼笋敞,深藏功與名......??.

出了什么問題?

我想上面的兩個場景出現(xiàn)的問題大家或多或少都會遇見捅儒,總結(jié)一下就是:

  1. 因為不同人負(fù)責(zé)不同模塊液样,調(diào)用他人必須了解他人編寫的模塊如何調(diào)用振亮,對象是啥巧还,初始化方式是啥,這違背了面向?qū)ο蟮姆庋b原則
  2. 引入不同的模塊頭文件坊秸,多了以后麸祷,所依賴的外部發(fā)生一丁點變化你就要跟著變,邏輯變得越來越耦合褒搔,不利于維護
  3. 調(diào)用不同模塊要反復(fù)與他人溝通傳參阶牍、回調(diào)流程喷面、接口定義等等,溝通效率低下
  4. 產(chǎn)品提出各種需求走孽,但是我寫的代碼都是差不多的惧辈,來一個頁面我需要寫一些相同邏輯的代碼,而且產(chǎn)品還抱怨每次加相同的東西就要改代碼發(fā)版磕瓷,這顯然不能滿足復(fù)用的要求盒齿。

總結(jié):
依賴多、耦合高困食、復(fù)用低边翁。
可我們都知道有這么句話啊:高內(nèi)聚硕盹、低耦合符匾,職責(zé)單一邏輯清晰。

路由就是解決上面的問題

我們已經(jīng)發(fā)現(xiàn)依賴比較大是因為要導(dǎo)入其他模塊的頭文件瘩例,了解其他模塊的邏輯和定義啊胶,如果多了,你的代碼中引入的頭文件或者導(dǎo)入的包名越來越多仰剿,改一下牽一發(fā)而動全身啊创淡。大概是這個樣子:

相互引用

依賴的問題很嚴(yán)重,要想破除這樣的依賴南吮,我們能想到的辦法就是找個調(diào)度中心去做這件事琳彩,其實各個業(yè)務(wù)模塊并不關(guān)心其他模塊具體的業(yè)務(wù)邏輯是什么,也不需要知道這個模塊如何獲取部凑,我只關(guān)心怎么調(diào)用和反饋的結(jié)果露乏,而這個有了調(diào)度中心這個東西,每個模塊不需要依賴其他模塊涂邀,只需要調(diào)度中心關(guān)心每個模塊的調(diào)度瘟仿。


引入中介者

有了Route這個調(diào)度中心,每個模塊就不用寫那么多重復(fù)的耦合代碼了比勉,也不需要在導(dǎo)入那么多頭文件了和引入那么多包名了劳较,這些藍(lán)色的箭頭代表著調(diào)用方式,如果調(diào)用方式再統(tǒng)一一下浩聋,溝通效率就提升上去了观蜗,因為我們可以用一套約定好的數(shù)據(jù)協(xié)議來代替重復(fù)溝通,有時候我們需要靠約定和協(xié)議來提高我們的工作效率衣洁。

Tips:
發(fā)現(xiàn)問題這個環(huán)節(jié)很重要墓捻,你在工作中經(jīng)常要反復(fù)做的,浪費時間的都是需要你去優(yōu)化和花大力氣去解決的坊夫,作為一個專業(yè)人士砖第,不斷改進你的代碼撤卢,優(yōu)化你的工作流程,帶動團隊向好的協(xié)作方式去轉(zhuǎn)型梧兼,這是專業(yè)人士的習(xí)慣放吩,更應(yīng)該成為你的習(xí)慣。同時針對代碼存在的問題羽杰,也許你經(jīng)常會隱隱約約感到有問題屎慢,就是不知道問題在什么地方,那么需要問問自己有沒有以下情況:哪些代碼是經(jīng)常寫且重復(fù)度很高的忽洛,是不是可以抽象出來腻惠?哪些代碼需要反復(fù)的變動,是不是可以做成配置或者是定義一套數(shù)據(jù)格式來滿足動態(tài)兼容欲虚?有沒有一些現(xiàn)成的設(shè)計模式可以解決這些問題集灌?比方說,調(diào)度中心則使用的是中介者模式复哆。我見過

為啥要說iOS路由呢欣喧?

路由層其實在邏輯功能上的設(shè)計都是一樣的,很多人把App中的視圖切換當(dāng)做是路由組件的功能職責(zé)梯找,這點我持否定態(tài)度唆阿,從單一職責(zé)角度和MVC框架分析來看,視圖切換屬于View中的交互邏輯并不屬于消息傳遞或者是事件分發(fā)的范疇锈锤,但路由請求驯鳖、視圖轉(zhuǎn)場的實現(xiàn)部分與Android平臺和iOS平臺上的導(dǎo)航機制有著非常緊密的關(guān)系,Android操作系統(tǒng)有著天然的架構(gòu)優(yōu)勢久免,Intent機制可以協(xié)助應(yīng)用間的交互與通訊浅辙,是對調(diào)用組件和數(shù)據(jù)傳遞的描述,本身這種機制就解除了代碼邏輯和界面之間的依賴關(guān)系阎姥,只有數(shù)據(jù)依賴记舆。而iOS的界面導(dǎo)航和轉(zhuǎn)場機制則大部分依賴UI組件各自的實現(xiàn),所以如何解決這個問題呼巴,iOS端路由的實現(xiàn)則比較有代表性泽腮。
其實說白一點,路由層解決的核心問題就是原來界面或者組件之間相互調(diào)用都必須相互依賴衣赶,需要導(dǎo)入目標(biāo)的頭文件诊赊、需要清楚目標(biāo)對象的邏輯,而現(xiàn)在全部都通過路由中轉(zhuǎn)屑埋,只依賴路由或者某種通訊協(xié)議豪筝,或者依靠一些消息傳遞機制連路由都不依賴痰滋。其次摘能,路由的核心邏輯就是目標(biāo)匹配续崖,對于外部調(diào)用的情況來說,URL如何匹配Handler是最為重要的团搞,匹配就必然用到正則表達(dá)式严望。了解這些關(guān)鍵點以后就有了設(shè)計的目的性,let‘s do it~

總結(jié)一下這個路由都要有什么逻恐?(需求分析)

我們先根據(jù)上面的模糊的總結(jié)梳理一下:

  1. 路由需要能夠?qū)崿F(xiàn)被其他模塊調(diào)度像吻,從而調(diào)度另外一個模塊
  2. 接入路由的模塊不需要知道目標(biāo)模塊的實現(xiàn)
  3. 調(diào)度發(fā)起方需要有目標(biāo)的響應(yīng)回調(diào),類似于http請求复隆,有一個request就要有一個response拨匆,才能實現(xiàn)雙向的調(diào)用
  4. 調(diào)用方式需要統(tǒng)一,統(tǒng)一而松散的調(diào)用協(xié)議和數(shù)據(jù)協(xié)議可以減少大量接入成本和溝通成本
    那一個完整的調(diào)度流程應(yīng)該是這樣的:
Route流程

看到這個流程以后挽拂,可以確定以下幾件事:

  1. A模塊調(diào)用路由惭每,為表達(dá)自己需要調(diào)用的是B模塊,考慮到H5亏栈、推送以及其他App的外部調(diào)用台腥,可以使用URL這種方式來定義目標(biāo),也就是說用URL來表示目標(biāo)B
  2. 對一個URL的請求來說绒北,路由需要有統(tǒng)一的回調(diào)處理黎侈,當(dāng)然,如果不需要回調(diào)也是可以的闷游,回調(diào)是需要目標(biāo)去觸發(fā)的
  3. 路由要有處理URL的功能峻汉,并調(diào)用其他模塊的能力

根據(jù)以上粗略的定義一下路由的框架:

框架類圖

這里面以供有4部分:
WLRRouter就是一個實體對象,用來提供給其他模塊調(diào)用脐往。
WLRRouteRequest是一個以URL為基礎(chǔ)的實體對象俱济,為什么不直接用URL字符串?因為考慮到如果路由在內(nèi)部調(diào)用其他模塊的時候需要傳入一些原生對象钙勃,而URL上只能攜帶類型單一的字符串鍵值對表示參數(shù)蛛碌,所以需要使用這么一個對象進行包裝。
WLRRouteHandler是一個處理某一個WLRRouteRequest請求的對象辖源,當(dāng)路由接收一個WLRRouteRequest請求蔚携,轉(zhuǎn)發(fā)給一個WLRRouteHandler處理,處理完畢以后如果有回調(diào)克饶,則回調(diào)給調(diào)用者酝蜒。URL的請求與Handler的對應(yīng)關(guān)系肯定需要匹配的邏輯,為了使得路由內(nèi)部邏輯更加清晰單獨使用WLRRouteMatcher來處理匹配的邏輯矾湃。

深入具體需求亡脑,細(xì)化功能實現(xiàn)(詳細(xì)設(shè)計)

有了粗略的需求分析接下來就是細(xì)化需求并給出詳細(xì)設(shè)計的階段了,其實編寫一個模塊要有系統(tǒng)性思維,粗略的需求里面包含了整個模塊要實現(xiàn)的主要核心功能霉咨,核心流程是什么蛙紫,要有哪幾個類才能實現(xiàn)這樣的流程,不要妄圖一下子深入到細(xì)枝末節(jié)上途戒,讓細(xì)節(jié)左右宏觀上的邏輯架構(gòu)坑傅,大腦不適合同時考慮宏觀和微觀的事情,尤其是對經(jīng)驗不太足的開發(fā)者來說喷斋,要逐漸學(xué)會大腦在不同的時期進行宏觀和微觀的無縫切換唁毒,這樣才能專注目標(biāo)和結(jié)果,在實現(xiàn)過程中再投入全部精力考慮細(xì)節(jié)星爪,才能保證具體的實現(xiàn)是不偏離總體目標(biāo)的浆西。

WLRRouteRequest設(shè)計

路由層的請求,無論是跨應(yīng)用的外部調(diào)用(H5調(diào)用顽腾、其他App調(diào)用)還是內(nèi)部調(diào)用(內(nèi)部模塊相互調(diào)用)室谚,最后都要形成一個路由請求,一個以URL為基礎(chǔ)的request對象崔泵,首先需要有攜帶URL秒赤,再一個要攜帶請求所需要的參數(shù),參數(shù)有三種憎瘸,一種是Url上的鍵值對參數(shù)入篮,一種是RESTFul風(fēng)格的Url上的路徑參數(shù),一種是內(nèi)部調(diào)用適用的原生參數(shù)幌甘,具體是:

WLRRouteRequest

這里說一下路徑參數(shù)潮售,很多有后端開發(fā)經(jīng)驗的人都知道筝家,一個url上傳遞參數(shù)乞封,或者是匹配后端服務(wù)的service,Url的路徑對于表達(dá)轉(zhuǎn)發(fā)語義十分重要琼懊,比方說 :
http://aaaa.com/login
http://aaaa.com/userCenter
那Url中的login和userCenter可以代表是哪個后端服務(wù)皱埠,那路由就需要設(shè)置正則匹配表達(dá)式去匹配http://aaaa.com/ 這部分肮帐,截取login、userCenter部分边器,說回我們的路由训枢,App的路由需要通過設(shè)置Url的正則表達(dá)式來獲取路徑參數(shù),同時我們必須知道這些參數(shù)的值和名稱忘巧,那么我可以這樣定義Url匹配的表達(dá)式
scheme://host/path/:name([a-zA-Z_-]+)
熟悉正則表達(dá)式的孩子都知道分組模式恒界,path后name是key,([a-zA-Z_-]+)是規(guī)定name對應(yīng)的value應(yīng)該是什么格式的砚嘴。那么routeParameters就是存放路徑參數(shù)的

//url
@property (nonatomic, copy, readonly) NSURL *URL;
//url上十酣?以后的鍵值對參數(shù)
@property (nonatomic, copy, readonly) NSDictionary *queryParameters;
//url上匹配的路徑參數(shù)
@property (nonatomic, copy, readonly) NSDictionary *routeParameters;
//原生參數(shù)涩拙,比方說要傳給目標(biāo)UIImage對象,NSArray對象等等
@property (nonatomic, copy, readonly) NSDictionary *primitiveParams;
//目標(biāo)預(yù)留的callBack block,當(dāng)完成處理以后,回到此Block耸采,完成調(diào)用者的回調(diào)
@property(nonatomic,copy)void(^targetCallBack)(NSError *error,id responseObject);
//是否消費掉兴泥,一個request只能處理一次,該字段反應(yīng)request是否被處理過
@property(nonatomic)BOOL isConsumed;

WLRRouteHandler設(shè)計

handler對象要接收一個WLRRouteRequest對象來啟動處理流程洋幻,前面經(jīng)過我們的分析,這個handler應(yīng)該擔(dān)負(fù)起通過url和參數(shù)獲取目標(biāo)對象的職責(zé)翅娶,在一般的route處理中文留,目標(biāo)往往是一個視圖控制器,先實現(xiàn)這樣一個通過url調(diào)用某一個視圖控制器的并跳轉(zhuǎn)處理的handler竭沫,那么應(yīng)該是如下的:

WLRRouteHandler

handler處理一個request請求是一個具有過程性的邏輯燥翅,WLRRouteHandler要作為一個基類,我們知道蜕提,這個handler在需要處理獲取目標(biāo)視圖控制器->參數(shù)傳遞給目標(biāo)視圖控制器->視圖控制器的轉(zhuǎn)場->完成回調(diào)森书,那么我們需要設(shè)計這樣的接口

//即將開始處理request請求,返回值決定是否要繼續(xù)相應(yīng)request
- (BOOL)shouldHandleWithRequest:(WLRRouteRequest *)request;
//開始處理request請求
-(BOOL)handleRequest:(WLRRouteRequest *)request error:(NSError *__autoreleasing *)error;
// 根據(jù)request獲取目標(biāo)控制器
-(UIViewController *)targetViewControllerWithRequest:(WLRRouteRequest *)request;
//轉(zhuǎn)場一定是從一個視圖控制器跳轉(zhuǎn)到另外一個視圖控制器谎势,該方法用以獲取轉(zhuǎn)場中的源視圖控制器
-(UIViewController *)sourceViewControllerForTransitionWithRequest:(WLRRouteRequest *)request;
//改方法內(nèi)根據(jù)request凛膏、獲取的目標(biāo)和源視圖控制器,完成轉(zhuǎn)場邏輯
-(BOOL)transitionWithWithRequest:(WLRRouteRequest *)request sourceViewController:(UIViewController *)sourceViewController targetViewController:(UIViewController *)targetViewController isPreferModal:(BOOL)isPreferModal error:(NSError *__autoreleasing *)error;
//根據(jù)request來返回是否是模態(tài)跳轉(zhuǎn)
- (BOOL)preferModalPresentationWithRequest:(WLRRouteRequest *)request;

WLRRouteMatcher設(shè)計

一個matcher應(yīng)該具有根據(jù)url和參數(shù)判斷是否匹配某個url表達(dá)式的邏輯

WLRRouteMatcher

matcher對象必須擁有url的匹配表達(dá)式脏榆,類似于 scheme://host/path/:name([a-zA-Z_-]+) 猖毫,也有擁有該表達(dá)式真正的正則表達(dá)式,^scheme://host/path/([a-zA-Z_-]+)$

@interface WLRRouteMatcher : NSObject
//url匹配表達(dá)式
@property(nonatomic,copy)NSString * routeExpressionPattern;
//url匹配的正則表達(dá)式
@property(nonatomic,copy)NSString * originalRouteExpression;
+(instancetype)matcherWithRouteExpression:(NSString *)expression;
-(WLRRouteRequest *)createRequestWithURL:(NSURL *)URL primitiveParameters:(NSDictionary *)primitiveParameters targetCallBack:(void(^)(NSError *, id responseObject))targetCallBack;

設(shè)計-(WLRRouteRequest *)createRequestWithURL:(NSURL *)URL primitiveParameters:(NSDictionary *)primitiveParameters targetCallBack:(void(^)(NSError *, id responseObject))targetCallBack;這個方法须喂,可以通過傳入url和參數(shù)吁断,檢查是否返回request請求,來表示該WLRRouteMatcher對象所擁有的匹配表達(dá)式與url是否能夠匹配坞生,這句話有點繞仔役,看不懂的多看幾遍。

WLRRouter

WLRRouter是路由實體對象是己,后端開發(fā)者對于路由掛載的概念非常了解又兵,其實這樣一個路由實體對象可以完成對URL的攔截和處理并返回結(jié)果,事實上卒废,根據(jù)前面的梳理和總結(jié)寒波,WLRRouter對象內(nèi)部應(yīng)該保存了需要匹配攔截的URL表達(dá)式,而前面我們知道Url的匹配表達(dá)式是存儲在WLRRouteMatcher對象中的升熊,并且一個Url傳入檢查是否匹配也是Matcher對象提供的功能俄烁,對于匹配上的Url需要有對應(yīng)的Handler處理,所以Router對象的內(nèi)部存在Machter對象和Handler對象一一對應(yīng)的關(guān)系级野,并且擁有注冊Url表達(dá)式對應(yīng)到Handler的功能页屠,也具有傳入Url和參數(shù)就能匹配到Handler的功能粹胯,還要有一個檢測Url是否能有對應(yīng)Handler處理的功能,所以應(yīng)該是:

WLRRouter

這里有兩種注冊的方法辰企,注冊handler的就不需再多描述风纠,另外一個是注冊Block的回調(diào)形式,因為有時候可能會需要一些簡單的Url攔截牢贸,去做一些事情竹观,這里面的Block需要返回一個request對象,這是因為潜索,如果Block沒有對request的回調(diào)做處理臭增,Router應(yīng)該處理調(diào)用者的回調(diào)問題,否則就會出現(xiàn)調(diào)用者設(shè)置了回調(diào)的Block而沒有人調(diào)用回來竹习,這樣就尷尬了誊抛。

/**
 注冊一個route表達(dá)式并與一個block處理相關(guān)聯(lián)
 
 @param routeHandlerBlock block用以處理匹配route表達(dá)式的url的請求
 @param route url的路由表達(dá)式,支持正則表達(dá)式的分組整陌,例如app://login/:phone({0,9+})是一個表達(dá)式拗窃,:phone代表該路徑值對應(yīng)的key,可以在WLRRouteRequest對象中的routeParameters中獲取
 */
-(void)registerBlock:(WLRRouteRequest *(^)(WLRRouteRequest * request))routeHandlerBlock forRoute:(NSString *)route;
/**
 注冊一個route表達(dá)式并與一個block處理相關(guān)聯(lián)
 
 @param routeHandlerBlock handler對象用以處理匹配route表達(dá)式的url的請求
 @param route url的路由表達(dá)式,支持正則表達(dá)式的分組泌辫,例如app://login/:phone({0,9+})是一個表達(dá)式随夸,:phone代表該路徑值對應(yīng)的key,可以在WLRRouteRequest對象中的routeParameters中獲取
 */
-(void)registerHandler:(WLRRouteHandler *)handler forRoute:(NSString *)route;

/**
 檢測url是否能夠被處理,不包含中間件的檢查

 @param url 請求的url
 @return 是否可以handle
 */
-(BOOL)canHandleWithURL:(NSURL *)url;
/**
 處理url請求

 @param URL 調(diào)用的url
 @param primitiveParameters 攜帶的原生對象
 @param targetCallBack 傳給目標(biāo)對象的回調(diào)block
 @param completionBlock 完成路由中轉(zhuǎn)的block
 @return 是否能夠handle
 */
-(BOOL)handleURL:(NSURL *)URL primitiveParameters:(NSDictionary *)primitiveParameters targetCallBack:(void(^)(NSError *, id responseObject))targetCallBack withCompletionBlock:(void(^)(BOOL handled, NSError *error))completionBlock;

梳理總結(jié):

從以上我們規(guī)劃的幾個類的接口震放,我們可以清楚的看到Router工作的流程逃魄。

  1. 首先實例化Router對象
  2. 實例化Handler或者是Block,通過Router的注冊接口使得一個Url的匹配表達(dá)式對應(yīng)一個Handler或者是一個block
  3. Router內(nèi)部會將Url的表達(dá)式形成一個Matcher對象進行保存澜搅,對應(yīng)的Handler或處理的Block會與Matcher一一對應(yīng)伍俘,怎么對應(yīng)呢?應(yīng)該使用路由表達(dá)式進行關(guān)聯(lián)
  4. Router通過handle方法勉躺,接收一個Url的請求癌瘾,內(nèi)部遍歷所有的Matcher對象,將Url和參數(shù)轉(zhuǎn)換為Request對象饵溅,如果能轉(zhuǎn)換為Request對象則說明能匹配妨退,如果不能則說明該Url不能被路由實體處理
  5. 拿到Request對象以后,則根據(jù)Matcher對應(yīng)的路由表達(dá)式找到對應(yīng)的Handler或者是Block
  6. 根據(jù)Handler的幾個關(guān)鍵方法蜕企,傳入Request對象咬荷,按照順序完成處理邏輯的觸發(fā),最后如果有request當(dāng)中包含有目標(biāo)的回調(diào)轻掩,則將處理結(jié)果通過回調(diào)的Block響應(yīng)給調(diào)用方
  7. Handler完成處理后幸乒,Router完成本次路由請求
WLRRoute

Tips:
很多開發(fā)者把敏捷開發(fā)當(dāng)做來了需求不管三七二十一,一把梭子就是干唇牧,不斷寫不斷改罕扎。??其實敏捷開發(fā)是一種模式聚唐,并不簡單是快速迭代的意思。初入行的程序員其實都是coder(編碼員)腔召,基本上在靠模仿代碼和代碼套路去工作杆查,真正的Programmer(程序設(shè)計師)是在設(shè)計代碼,科班出身的程序員往往在進階過程中突然發(fā)現(xiàn)大學(xué)里面的軟件工程有多么重要臀蛛,其實設(shè)計能力的培養(yǎng)需要有一個正規(guī)的流程亲桦,就像本教程的大綱一樣,發(fā)現(xiàn)問題->需求分析->總體設(shè)計->具體實現(xiàn)->測試->發(fā)布維護浊仆,有了清晰的流程客峭,把你的精力和時間按照不同的階段進行分配和投入,你就會豁然開朗氧卧,比方說在總體設(shè)計過程中桃笙,就需要你以宏觀的功能流程去考慮大體的模塊有幾個氏堤,模塊的關(guān)系是怎樣沙绝,每個模塊的核心職責(zé)是什么,建議根據(jù)需求去畫一個邏輯流程圖鼠锈,將每個邏輯分支都補全闪檬,再根據(jù)流程圖規(guī)劃總體框架,總體框架通過類圖來表達(dá)购笆,每個類的屬性和行為都確定以后粗悯,再進入具體設(shè)計階段就非常輕松和容易了,同時類圖畫完同欠,技術(shù)方案的可行性和實現(xiàn)所需時間也就非常容易精確評估了

給架子填充骨血(具體實現(xiàn)):

有了上面大體上的架子我們就能相信只要按照這個架子样傍,就能完成你在需求分析階段規(guī)劃的功能目標(biāo),現(xiàn)在我們要做的就是在我們設(shè)計的這個牛逼的框架里填充血肉铺遂,去實現(xiàn)它衫哥,這部分是一個非常有意思的過程,在上一步你已經(jīng)獲得了相當(dāng)大的信心襟锐,在這一步你只需要按照規(guī)定盡力去實現(xiàn)撤逢,在有信心的情況下,你會思維活躍粮坞,因為你明確了要實現(xiàn)何種功能的目標(biāo)蚊荣,大腦會自動根據(jù)目標(biāo)和現(xiàn)在差距不斷想考出各種辦法去彌補這樣的差距,你所做的就是不斷嘗試你大腦迸發(fā)出的這些代碼莫杈,選擇最有效互例、可讀性最好、性能最好和代碼最健壯的代碼筝闹。

WLRRouteRequest:

了解了以上敲霍,我們從WLRRouteRequest入手俊马。
其實WLRRouteRequest跟NSURLRequest差不多,不過WLRRouteRequest繼承NSObject肩杈,實現(xiàn)NSCopying協(xié)議柴我,我們再來看一下頭文件的聲明:

#import <Foundation/Foundation.h>

@interface WLRRouteRequest : NSObject<NSCopying>
//外部調(diào)用的URL
@property (nonatomic, copy, readonly) NSURL *URL;
//URL表達(dá)式,比方說調(diào)用登錄界面的表達(dá)式可以為:AppScheme://user/login/138********扩然,那URL的匹配表達(dá)式可以是:/login/:phone([0-9]+),路徑必須以/login開頭艘儒,后面接0-9的電話號碼數(shù)字,當(dāng)然你也可以直接把電話號碼的正則匹配寫全
@property(nonatomic,copy)NSString * routeExpression;
//如果URL是AppScheme://user/login/138********?callBack="",那么這個callBack就出現(xiàn)在這
@property (nonatomic, copy, readonly) NSDictionary *queryParameters;
//這里面會出現(xiàn){@"phone":@"138********"}
@property (nonatomic, copy, readonly) NSDictionary *routeParameters;
//這里面存放的是內(nèi)部調(diào)用傳遞的原生參數(shù)
@property (nonatomic, copy, readonly) NSDictionary *primitiveParams;
//自動檢測竊取回調(diào)的callBack 的Url
@property (nonatomic, strong) NSURL *callbackURL;
//目標(biāo)的viewcontrolller或者是組件可以通過這個
@property(nonatomic,copy)void(^targetCallBack)(NSError *error,id responseObject);
//用以表明該request是否被消費
@property(nonatomic)BOOL isConsumed;
//簡便方法夫偶,用以下標(biāo)法取參數(shù)
- (id)objectForKeyedSubscript:(NSString *)key;
//初始化方法
-(instancetype)initWithURL:(NSURL *)URL routeExpression:(NSString *)routeExpression routeParameters:(NSDictionary *)routeParameters primitiveParameters:(NSDictionary *)primitiveParameters targetCallBack:(void(^)(NSError * error,id responseObject))targetCallBack;
-(instancetype)initWithURL:(NSURL *)URL;
//默認(rèn)完成目標(biāo)的回調(diào)
-(void)defaultFinishTargetCallBack;
@end

初始化方法就是將三個存放入?yún)⒌淖值涑跏蓟缯觯⒅苯訉rl上的?以后的參數(shù)取出來:

-(instancetype)initWithURL:(NSURL *)URL{
    if (!URL) {
        return nil;
    }
    self = [super init];
    if (self) {
        _URL = URL;
        _queryParameters = [[_URL query] WLRParametersFromQueryString];
    }
    return self;
}
-(instancetype)initWithURL:(NSURL *)URL routeExpression:(NSString *)routeExpression routeParameters:(NSDictionary *)routeParameters primitiveParameters:(NSDictionary *)primitiveParameters targetCallBack:(void (^)(NSError *, id))targetCallBack{
    if (!URL) {
        return nil;
    }
    self = [super init];
    if (self) {
        _URL = URL;
        _queryParameters = [[_URL query] WLRParametersFromQueryString];
        _routeExpression = routeExpression;
        _routeParameters = routeParameters;
        _primitiveParams = primitiveParameters;
        self.targetCallBack = targetCallBack;
    }
    return self;
}

在調(diào)用方設(shè)置傳入callBack的時候兵拢,因為request是消費型的翻斟,所以將TargetCallBack重新包裝,在回調(diào)的時候需要將isConsumed屬性設(shè)置為YES说铃,表示該request已經(jīng)被處理消耗访惜,這里實現(xiàn)的比較簡單,其實request是應(yīng)該具有狀態(tài)的腻扇,比方說未處理债热,處理中,已處理幼苛,實現(xiàn)一個優(yōu)雅的狀態(tài)機會更好的表達(dá)邏輯:

-(void)setTargetCallBack:(void (^)(NSError *, id))targetCallBack{
    __weak WLRRouteRequest * weakRequest = self;
    if (targetCallBack == nil) {
        return;
    }
    self.isConsumed = NO;
    _targetCallBack = ^(NSError *error, id responseObject){
        weakRequest.isConsumed = YES;
        targetCallBack(error,responseObject);
    };
    
}

默認(rèn)的回調(diào)方法是為了處理響應(yīng)者沒有觸發(fā)回調(diào)窒篱,則需要有默認(rèn)的回調(diào)給調(diào)用者:

-(void)defaultFinishTargetCallBack{
    if (self.targetCallBack && self.isConsumed == NO) {
        self.targetCallBack(nil,@"正常執(zhí)行回調(diào)");
    }
}

WLRRouteHandler

#import <Foundation/Foundation.h>
@class WLRRouteRequest;
@interface WLRRouteHandler : NSObject
//即將handle某一個請求
- (BOOL)shouldHandleWithRequest:(WLRRouteRequest *)request;
//根據(jù)request取出調(diào)用的目標(biāo)視圖控制器
-(UIViewController *)targetViewControllerWithRequest:(WLRRouteRequest *)request;
//根據(jù)request取出來源的視圖控制器
-(UIViewController *)sourceViewControllerForTransitionWithRequest:(WLRRouteRequest *)request;
//開始進行轉(zhuǎn)場
-(BOOL)transitionWithWithRequest:(WLRRouteRequest *)request sourceViewController:(UIViewController *)sourceViewController targetViewController:(UIViewController *)targetViewController isPreferModal:(BOOL)isPreferModal error:(NSError *__autoreleasing *)error;
//是否模態(tài)跳轉(zhuǎn)
- (BOOL)preferModalPresentationWithRequest:(WLRRouteRequest *)request;
@end

當(dāng)WLRRouter對象完成了URL的匹配生成Request,并尋找到Handler的時候舶沿,首先會調(diào)用

-(BOOL)shouldHandleWithRequest:(WLRRouteRequest *)request{
    return YES;
}

子類可以覆寫這個方法墙杯,實現(xiàn)一些參數(shù)檢查或者當(dāng)然App狀態(tài)檢查的工作,比方說檢測當(dāng)前界面是否已經(jīng)有模態(tài)跳轉(zhuǎn)覆蓋的界面或者是彈窗之類括荡,做一些清場工作來為本次轉(zhuǎn)場進行準(zhǔn)備高镐。隨后調(diào)用調(diào)用:

-(BOOL)handleRequest:(WLRRouteRequest *)request error:(NSError *__autoreleasing *)error{
    UIViewController * sourceViewController = [self sourceViewControllerForTransitionWithRequest:request];
    UIViewController * targetViewController = [self targetViewControllerWithRequest:request];
    if ((![sourceViewController isKindOfClass:[UIViewController class]])||(![targetViewController isKindOfClass:[UIViewController class]])) {
        *error = [NSError WLRTransitionError];
        return NO;
    }
    if (targetViewController != nil) {
        targetViewController.wlr_request = request;
    }
    BOOL isPreferModal = [self preferModalPresentationWithRequest:request];
    return [self transitionWithWithRequest:request sourceViewController:sourceViewController targetViewController:targetViewController isPreferModal:isPreferModal error:error];
}

注意targetViewController.wlr_request = request這一句,在獲取目標(biāo)控制器以后一汽,我們會將request對象賦值給目標(biāo)控制器避消,這是一種正向賦值的做法,為了降低接入成本召夹,創(chuàng)建一個UIViewController的擴展岩喷,添加wlr_request這么一個屬性,這么做也是不得已為之监憎,因為UIKit庫中ViewController并沒有上下文機制纱意,也就不能通過無入侵的形式進行消息傳遞。
在此我們準(zhǔn)備好轉(zhuǎn)場所需要的一切鲸阔,包括是否模態(tài)跳轉(zhuǎn)偷霉,目標(biāo)控制器迄委,源控制器,request等等类少,緊接著就開始進行轉(zhuǎn)場:

-(BOOL)transitionWithWithRequest:(WLRRouteRequest *)request sourceViewController:(UIViewController *)sourceViewController targetViewController:(UIViewController *)targetViewController isPreferModal:(BOOL)isPreferModal error:(NSError *__autoreleasing *)error;{
    if (isPreferModal||![sourceViewController isKindOfClass:[UINavigationController class]]) {
        [sourceViewController presentViewController:targetViewController animated:YES completion:nil];
    }
    else if ([sourceViewController isKindOfClass:[UINavigationController class]]){
        UINavigationController * nav = (UINavigationController *)sourceViewController;
        [nav pushViewController:targetViewController animated:YES];
    }
    return YES;
}

這里著重說一下handler的targetViewControllerWithRequest和sourceViewController方法叙身,你可能會覺得,我要為每一個視圖控制器配置一子類對象硫狞,在targetViewControllerWithRequest里面寫上獲取目標(biāo)控制器的方法信轿,會造成繼承很多很多handler,實際上你可以發(fā)揮一下想象力残吩,在targetViewControllerWithRequest方法里财忽,你已經(jīng)能拿到request,而request里面有路由表達(dá)式泣侮,實際上一個路由表達(dá)式應(yīng)該對應(yīng)一個或者多個視圖控制器即彪,你完全可以做一個配置文件,在targetViewControllerWithRequest方法里通過路由表達(dá)式來獲取目標(biāo)視圖控制器的初始化信息活尊,目標(biāo)控制器的初始化無非分為直接代碼初始化隶校,要么是storyboard初始化,要么是xibs初始化酬凳,但是這個配置文件如何設(shè)計和使用惠况,不在本文討論范疇遭庶,需要你開動腦筋去解決宁仔。
同樣preferModalPresentationWithRequest也可以根據(jù)配置來處理是否要進行模態(tài)跳轉(zhuǎn)的問題。transitionWithRequest方法里面根據(jù)source來進行導(dǎo)航push或者是模態(tài)跳轉(zhuǎn)峦睡,你大可以繼承一下翎苫,然后重寫轉(zhuǎn)場過程。
Handler類里面的生命周期函數(shù)是不是很像UIViewControllerContextTransitioning轉(zhuǎn)場上下文的協(xié)議的設(shè)定榨了?- (nullable __kindof UIViewController *)viewControllerForKey:(UITransitionContextViewControllerKey)key;方法使上下文提供目標(biāo)控制器和源控制器煎谍,其實在handler中你完全可以自定義一個子類,在transitionWithRequest方法里龙屉,設(shè)置遵守UIViewControllerTransitioningDelegate的代理呐粘,然后在此提供遵守 UIViewControllerAnimatedTransitioning的動畫控制器,然后自定義轉(zhuǎn)場上下文转捕,實現(xiàn)自定義UI轉(zhuǎn)場作岖,而對應(yīng)的匹配邏輯是與此無關(guān)的,我們就可以在路由曾控制全局的頁面轉(zhuǎn)場效果五芝。對自定義轉(zhuǎn)場不太熟悉的同學(xué)請移步我之前的文章:
ContainerViewController的ViewController 轉(zhuǎn)場

WLRRouteMatcher

#import <Foundation/Foundation.h>
@class WLRRouteRequest;
@interface WLRRouteMatcher : NSObject
//傳入URL匹配的表達(dá)式痘儡,獲取一個matcher實例
+(instancetype)matcherWithRouteExpression:(NSString *)expression;
//傳入URL,如果能匹配上枢步,則生成WLRRouteRequest對象沉删,同時將各種參數(shù)解析好交由WLRRouteRequest攜帶
-(WLRRouteRequest *)createRequestWithURL:(NSURL *)URL primitiveParameters:(NSDictionary *)primitiveParameters targetCallBack:(void(^)(NSError *, id responseObject))targetCallBack;
@end

Matcher對象需要傳入一個URL對象來判斷是否匹配渐尿,生成request對象,說到匹配就一定不會傻傻的用字符串相等矾瑰,肯定是要借助正則表達(dá)式來判斷是否匹配和將分組的內(nèi)容從字符串中取出砖茸,我們初始化Macter對象的時候要分配一個Url表達(dá)式給它,格式前面提到過殴穴,Url諸如http://abc.com/:name([a-zA-Z0-9-]+) 的格式并不是真正的標(biāo)準(zhǔn)正則表達(dá)式渔彰,如果要把name取出,則必須將其裝換為標(biāo)準(zhǔn)的正則表達(dá)式像:^http://abc.com/([a-zA-Z0-9-]+)$ 這樣推正,那么就需要進行轉(zhuǎn)換和提取:后面的key值恍涂,在Fundaction庫中,NSRegularExpression是用來處理正則表達(dá)式的植榕,為了使邏輯更加清晰再沧,我們可以把這樣的邏輯通過繼承NSRegularExpression封裝到一個叫WLRRegularExpression的類中,這個類提供匹配和轉(zhuǎn)換標(biāo)準(zhǔn)正則表達(dá)式的功能如下:

WLRRegularExpression

匹配的結(jié)果我們可以通過一個WLRMatchResult來表示尊残。傳入路由url的匹配表達(dá)式穿實話WLRRegularExpression實例炒瘸,在初始化的時候就轉(zhuǎn)換為標(biāo)準(zhǔn)的正則表達(dá)式和將:后面的key的名字保存到routerParamNamesArr當(dāng)中,matchResultForString接口可接受一個Url從而生成匹配結(jié)果的對象寝衫,匹配結(jié)果的對象里存儲著路徑參數(shù)信息和是否匹配的結(jié)果顷扩。

#import <Foundation/Foundation.h>
@class WLRMatchResult;
@interface WLRRegularExpression : NSRegularExpression
//傳入一個URL返回一個匹配結(jié)果
-(WLRMatchResult *)matchResultForString:(NSString *)string;
//根據(jù)一個URL的表達(dá)式創(chuàng)建一個WLRRegularExpression實例
+(WLRRegularExpression *)expressionWithPattern:(NSString *)pattern;
@end

WLRRegularExpression繼承NSRegularExpression

-(instancetype)initWithPattern:(NSString *)pattern options:(NSRegularExpressionOptions)options error:(NSError * _Nullable __autoreleasing *)error{
//初始化方法中將URL匹配的表達(dá)式pattern轉(zhuǎn)換為真正的正則表達(dá)式
    NSString *transformedPattern = [WLRRegularExpression transfromFromPattern:pattern];
//用轉(zhuǎn)化后的結(jié)果初始化父類
    if (self = [super initWithPattern:transformedPattern options:options error:error]) {
//同時將需要提取的子串的值的Key保存到數(shù)組中
        self.routerParamNamesArr = [[self class] routeParamNamesFromPattern:pattern];
    }
    return self;
}
//轉(zhuǎn)換為正則表達(dá)式
+(NSString*)transfromFromPattern:(NSString *)pattern{
//將pattern拷貝
    NSString * transfromedPattern = [NSString stringWithString:pattern];
//利用:[a-zA-Z0-9-_][^/]+這個正則表達(dá)式,將URL匹配的表達(dá)式的子串key提取出來慰毅,也就是像 /login/:phone([0-9]+)/:name[a-zA-Z-_]這樣的pattern隘截,需要將:phone([0-9]+)和:name[a-zA-Z-_]提取出來
    NSArray * paramPatternStrings = [self paramPatternStringsFromPattern:pattern];
    NSError * err;
//再根據(jù):[a-zA-Z0-9-_]+這個正則表達(dá)式,將帶有提取子串的key全部去除汹胃,比如將:phone([0-9]+)去除:phone改成([0-9]+)
    NSRegularExpression * paramNamePatternEx = [NSRegularExpression regularExpressionWithPattern:WLRRouteParamNamePattern options:NSRegularExpressionCaseInsensitive error:&err];
    for (NSString * paramPatternString in paramPatternStrings) {
        NSString * replaceParamPatternString = [paramPatternString copy];
        NSTextCheckingResult * foundParamNamePatternResult =[paramNamePatternEx matchesInString:paramPatternString options:NSMatchingReportProgress range:NSMakeRange(0, paramPatternString.length)].firstObject;
        if (foundParamNamePatternResult) {
            NSString *paramNamePatternString =[paramPatternString substringWithRange: foundParamNamePatternResult.range];
            replaceParamPatternString = [replaceParamPatternString stringByReplacingOccurrencesOfString:paramNamePatternString withString:@""];
        }
        if (replaceParamPatternString.length == 0) {
            replaceParamPatternString = WLPRouteParamMatchPattern;
        }
        transfromedPattern = [transfromedPattern stringByReplacingOccurrencesOfString:paramPatternString withString:replaceParamPatternString];
    }
    if (transfromedPattern.length && !([transfromedPattern characterAtIndex:0] == '/')) {
        transfromedPattern = [@"^" stringByAppendingString:transfromedPattern];
    }
//最后結(jié)尾要用$符號
    transfromedPattern = [transfromedPattern stringByAppendingString:@"$"];
//最后會將/login/:phone([0-9]+)轉(zhuǎn)換為login/([0-9]+)$
    return transfromedPattern;
}

在Matcher對象匹配一個URL的時候

-(WLRMatchResult *)matchResultForString:(NSString *)string{
//首先通過自身方法將URL進行匹配得出NSTextCheckingResult結(jié)果的數(shù)組
    NSArray * array = [self matchesInString:string options:0 range:NSMakeRange(0, string.length)];
    WLRMatchResult * result = [[WLRMatchResult alloc]init];
    if (array.count == 0) {
        return result;
    }
    result.match = YES;
    NSMutableDictionary * paramDict = [NSMutableDictionary dictionary];
//遍歷NSTextCheckingResult結(jié)果
    for (NSTextCheckingResult * paramResult in array) {
//再便利根據(jù)初始化的時候提取的子串的Key的數(shù)組
        for (int i = 1; i<paramResult.numberOfRanges&&i <= self.routerParamNamesArr.count;i++ ) {
            NSString * paramName = self.routerParamNamesArr[i-1];
//將值取出婶芭,然后將key和value放入到paramDict
            NSString * paramValue = [string substringWithRange:[paramResult rangeAtIndex:i]];
            [paramDict setObject:paramValue forKey:paramName];
        }
    }
//最后賦值給WLRMatchResult對象
    result.paramProperties = paramDict;
    return result;
}

Ok,有了WLRRegularExpression和WLRMatchResult以后着饥,WLRRouteMatcher可以接下來補充犀农,屬性有如下:

//scheme
@property(nonatomic,copy) NSString * scheme;
//WLRRegularExpression的實例 
@property(nonatomic,strong)WLRRegularExpression * regexMatcher;
//匹配的表達(dá)式
@property(nonatomic,copy)NSString * routeExpressionPattern;

初始化方法將形成WLRRegularExpression對象:

-(instancetype)initWithRouteExpression:(NSString *)routeExpression{
    if (![routeExpression length]) {
        return nil;
    }
    if (self = [super init]) {
//將scheme與path部分分別取出
        NSArray * parts = [routeExpression componentsSeparatedByString:@“://“];
        _scheme = parts.count>1?[parts firstObject]:nil;
        _routeExpressionPattern =[parts lastObject];
//將path部分當(dāng)做URL匹配表達(dá)式生成WLRRegularExpression實例
        _regexMatcher = [WLRRegularExpression expressionWithPattern:_routeExpressionPattern];
    }
    return self;
}

匹配方法就是通過WLRRegularExpression對象來生成result來檢測是否能夠匹配,如果能匹配則生成WLRRouteRequest對象:

-(WLRRouteRequest *)createRequestWithURL:(NSURL *)URL primitiveParameters:(NSDictionary *)primitiveParameters targetCallBack:(void (^)(NSError *, id))targetCallBack{
    NSString * urlString = [NSString stringWithFormat:@“%@%@“,URL.host,URL.path];
    if (self.scheme.length && ![self.scheme isEqualToString:URL.scheme]) {
        return nil;
    }
//調(diào)用self.regexMatcher將URL傳入宰掉,獲取WLRMatchResult結(jié)果呵哨,看是否匹配
    WLRMatchResult * result = [self.regexMatcher matchResultForString:urlString];
    if (!result.isMatch) {
        return nil;
    }
//如果匹配,則將result.paramProperties路徑參數(shù)傳入轨奄,初始化一個WLRRouteRequest實例
    WLRRouteRequest * request = [[WLRRouteRequest alloc]initWithURL:URL routeExpression:self.routeExpressionPattern routeParameters:result.paramProperties primitiveParameters:primitiveParameters targetCallBack:targetCallBack];
    return request;
}

告一段落孟害,這部分其實算是路由組件中比較核心的邏輯,為什么要拆成三個類戚绕,大家思考一下分割邏輯的思想纹坐,職責(zé)單一以后,你才會聚焦目的本身。

WLRRouter

完成了以上幾個類的實現(xiàn)耘子,WLRRouter就比較容易了果漾。

@class WLRRouteRequest;
@class WLRRouteHandler;
@interface WLRRouter : NSObject
//注冊block回調(diào)的URL匹配表達(dá)式,可用作內(nèi)部調(diào)用
-(void)registerBlock:(WLRRouteRequest *(^)(WLRRouteRequest * request))routeHandlerBlock forRoute:(NSString *)route;
//注冊一個WLRRouteHandler對應(yīng)的URL匹配表達(dá)式route
-(void)registerHandler:(WLRRouteHandler *)handler forRoute:(NSString *)route;
//判斷url是否可以被handle
-(BOOL)canHandleWithURL:(NSURL *)url;
-(void)setObject:(id)obj forKeyedSubscript:(NSString *)key;
-(id)objectForKeyedSubscript:(NSString *)key;
//調(diào)用handleURL方法谷誓,傳入URL绒障、原生參數(shù)和targetCallBack和完成匹配的completionBlock
-(BOOL)handleURL:(NSURL *)URL primitiveParameters:(NSDictionary *)primitiveParameters targetCallBack:(void(^)(NSError *, id responseObject))targetCallBack withCompletionBlock:(void(^)(BOOL handled, NSError *error))completionBlock;

在實現(xiàn)部分,有三個屬性捍歪,這三個屬性用來設(shè)置matcher户辱、handler、block的對應(yīng)關(guān)系糙臼,key都為路由表達(dá)式:

//每一個URL的匹配表達(dá)式對應(yīng)一個matcher實例庐镐,放在字典中
@property(nonatomic,strong)NSMutableDictionary * routeMatchers;
//每一個URL匹配表達(dá)式route對應(yīng)一個WLRRouteHandler實例
@property(nonatomic,strong)NSMutableDictionary * routeHandles;
//每一個URL匹配表達(dá)式route對應(yīng)一個回調(diào)的block
@property(nonatomic,strong)NSMutableDictionary * routeblocks;

在Router注冊Handler和回調(diào)的block的時候:

-(void)registerBlock:(WLRRouteRequest *(^)(WLRRouteRequest *))routeHandlerBlock forRoute:(NSString *)route{
    if (routeHandlerBlock && [route length]) {
//首先添加一個WLRRouteMatcher實例
        [self.routeMatchers setObject:[WLRRouteMatcher matcherWithRouteExpression:route] forKey:route];
//刪除route對應(yīng)的handler對象
        [self.routeHandles removeObjectForKey:route];
//將routeHandlerBlock和route存入對應(yīng)關(guān)系的字典中
        self.routeblocks[route] = routeHandlerBlock;
    }
}
-(void)registerHandler:(WLRRouteHandler *)handler forRoute:(NSString *)route{
    if (handler && [route length]) {
//首先生成route對應(yīng)的WLRRouteMatcher實例
        [self.routeMatchers setObject:[WLRRouteMatcher matcherWithRouteExpression:route] forKey:route];
//刪除route對應(yīng)的block回調(diào)
        [self.routeblocks removeObjectForKey:route];
//設(shè)置route對應(yīng)的handler
        self.routeHandles[route] = handler;
    }
}

接下來完善handle方法,設(shè)置completionHandler可以監(jiān)聽Router的每一次handle動作:

-(BOOL)handleURL:(NSURL *)URL primitiveParameters:(NSDictionary *)primitiveParameters targetCallBack:(void(^)(NSError *error, id responseObject))targetCallBack withCompletionBlock:(void(^)(BOOL handled, NSError *error))completionBlock{
    if (!URL) {
        return NO;
    }
    NSError * error;
    WLRRouteRequest * request;
    __block BOOL isHandled = NO;
//遍歷routeMatchers中的WLRRouteMatcher對象变逃,將URL傳入對象必逆,看是否能得到WLRRouteRequest對象
    for (NSString * route in self.routeMatchers.allKeys) {
        WLRRouteMatcher * matcher = [self.routeMatchers objectForKey:route];
        WLRRouteRequest * request = [matcher createRequestWithURL:URL primitiveParameters:primitiveParameters targetCallBack:targetCallBack];
        if (request) {
//如果得到WLRRouteRequest對象,說明匹配成功揽乱,則進行handler的生命周期函數(shù)調(diào)用或是這block回調(diào)
            isHandled = [self handleRouteExpression:route withRequest:request error:&error];
            break;
        }
    }
    if (!request) {
        error = [NSError WLRNotFoundError];
    }
//在調(diào)用完畢block或者是handler的生命周期方法以后名眉,回調(diào)完成的completionHandler
    [self completeRouteWithSuccess:isHandled error:error completionHandler:completionBlock];
    return isHandled;
}
//根據(jù)request進行handler的生命周期函數(shù)調(diào)用或者是block回調(diào)
-(BOOL)handleRouteExpression:(NSString *)routeExpression withRequest:(WLRRouteRequest *)request error:(NSError *__autoreleasing *)error {
    id handler = self[routeExpression];
//self.routeHandles和self.routeblocks拿到route對應(yīng)的回調(diào)block或者是handler實例
    if ([handler isKindOfClass:NSClassFromString(@"NSBlock")]) {
        WLRRouteRequest *(^blcok)(WLRRouteRequest *) = handler;
//調(diào)用回調(diào)的block
        WLRRouteRequest * backRequest = blcok(request);
//判斷block里面是否消費了此request,如果沒有而目標(biāo)設(shè)置了目標(biāo)回調(diào)targetCallBack凰棉,那么在此進行默認(rèn)回調(diào)
        if (backRequest.isConsumed==NO) {
            if (backRequest.targetCallBack) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    backRequest.targetCallBack(nil,nil);
                });
            }
        }
        return YES;
    }
    else if ([handler isKindOfClass:[WLRRouteHandler class]]){
//拿到url對應(yīng)的handler對象后损拢,先調(diào)用handler的shouldHandleWithRequest方法,如果返回YES撒犀,則調(diào)用進行轉(zhuǎn)場的transitionWithRequest方法
        WLRRouteHandler * rHandler = (WLRRouteHandler *)handler;
        if (![rHandler shouldHandleWithRequest:request]) {
            return NO;
        }
       return [rHandler transitionWithRequest:request error:error];
    }
    return YES;
}

以上我們可以看到福压,Router將匹配的邏輯單獨封裝到WLRRouteMatcher對象中,將匹配后的結(jié)果生成WLRRouteRequest實例以攜帶足夠完整的數(shù)據(jù)绘证,同時將真正處理視圖控制器的轉(zhuǎn)場或者是組件的加載或者是未來可能拓展的handler業(yè)務(wù)封裝到WLRRouteHandler實例中隧膏,匹配邏輯對應(yīng)的處理邏輯干凈分離哗讥,Matcher對象將匹配邏輯單獨封裝嚷那,如果有一天需要增加多種URL的匹配邏輯,則可以更換Matcher或者添加Matcher就可以杆煞,處理邏輯可以通過繼承擴展或者沖洗WLRRouteHandler的生命周期函數(shù)來更好的處理回調(diào)業(yè)務(wù)魏宽。如果WLRRouteHandler不能提供足夠多的擴展性,則可以使用block回調(diào)最大限度的進行擴展决乎。
以上队询,就是路由部分的整體實現(xiàn)。

Tips:
在具體的實現(xiàn)的過程中构诚,不要被總體設(shè)計給套死蚌斩,前面設(shè)計了幾個類,設(shè)計了幾個層次以后范嘱,就畫地為牢送膳,在實現(xiàn)具體細(xì)節(jié)的時候仍可以繼續(xù)進行局部設(shè)計员魏,分割邏輯,控制代碼復(fù)雜度叠聋,一方面你要注重在局部進行進一步設(shè)計和分割的時候帶來的究竟是什么撕阎,可讀性?可維護性碌补?還是幫助你能夠進行思考虏束?有時候,注釋可能更加能給你的邏輯帶來清晰的表述厦章,畢竟幾千行的代碼很正常镇匀,如果沒有經(jīng)驗,可能邏輯重點記得不太清楚袜啃,那就需要動動筆頭好好記錄一下坑律,再進一步實現(xiàn),將復(fù)雜邏輯進行適當(dāng)分割囊骤,是一種常見的策略

最終的路由整體類圖和如何設(shè)計代碼的總結(jié):

WLRRoute

整體的設(shè)計就是如此晃择,我們走了一趟從發(fā)現(xiàn)問題到最后通過封裝一個路由組件解決問題的過程,在這里也物,對于新手程序員我想討論一些有意義的東西宫屠,也就是手把手寫出一個路由的過程中,思路從何而來滑蚯?代碼設(shè)計怎么從無到有浪蹂?對于一個邏輯問題怎么轉(zhuǎn)換為代碼實踐?授之以魚不如授之以漁告材,在這個過程中我們總結(jié)一下哪些是比較重要的:

  1. 對于經(jīng)常重復(fù)的工作和低效的工作流程有沒有敏銳的察覺坤次,是否有想解決的愿望,畢竟能解決了你能節(jié)省更多時間斥赋,何樂而不為缰猴?可有些人寧愿加班做一些簡單的重復(fù)工作也不希望直面挑戰(zhàn)創(chuàng)造一些解決問題的新方式
  2. 在思考問題的解決方案的時候,大腦有沒有將問題抽象一下去尋找有沒有相應(yīng)解決問題的模式疤剑、方法論或者是方案滑绒?(比如說發(fā)現(xiàn)路由方案,發(fā)現(xiàn)中介者模式隘膘,發(fā)現(xiàn)分割思想和職責(zé)單一疑故,發(fā)現(xiàn)軟件工程的流程)
  3. 天賦并不是最重要的,甚至天賦是一種結(jié)論而不是條件弯菊,大腦不擅長同時處理很多事情纵势,在你設(shè)計或者編碼的過程中,通過標(biāo)準(zhǔn)化流程能最大程度的提高大腦思考的效率,比方說钦铁,總結(jié)問題就是總結(jié)問題而不要變總結(jié)變思考解決方案扫茅,否則會片面或者影響問題的客觀描述,總結(jié)問題->需求分析->總體設(shè)計->具體實現(xiàn)育瓜,這就是一個簡單的大腦思考流程葫隙,如果想不清楚,通過思維導(dǎo)圖躏仇、流程圖恋脚、UML建模等等,去把大腦需要同時照顧到的東西呈現(xiàn)在眼前焰手,你只要去對比對照糟描,一步步來就可以了,并不是有人特別有天賦书妻,而是有人比你有方法
  4. 發(fā)散很重要船响,每當(dāng)你從出現(xiàn)問題到解決問題以后,你可以安心的天馬行空的頭腦風(fēng)暴躲履,在這個過程中你可能對解決問題的方式進行重新審視和回顧见间,發(fā)現(xiàn)不足,甚至可以對其擴展使其能解決更多問題工猜,就比如你會考慮路由的效率米诉、安全,從而誕生中間件篷帅、異步事件的想法史侣,這點很重要,但卻很簡單
  5. 自我引導(dǎo)能力魏身,看了文章一步步做了出來惊橱,其實就跟做菜一樣,你學(xué)會了一道菜該怎么做箭昵,但為什么別人能做税朴,或者說別人從無到有的過程經(jīng)歷了什么,你為什么沒想到宙枷,這種想法會激勵你自我引導(dǎo)掉房,從而提升自己,進一步改變自己慰丛,如果你從來沒有對他人為什么能做到感到好奇,那就說明你的自我意識和自我引導(dǎo)力不夠強瘾杭,需要停下每天寫代碼的手诅病,認(rèn)真思考一下人生了

路由的安全

有兩個方面可以去做

  1. WLRRouteHandler實例中, -(BOOL)shouldHandleWithRequest:(WLRRouteRequest *)request中可以檢測request中的參數(shù),比方說效驗source或者是效驗業(yè)務(wù)參數(shù)完整等
  2. WLRRouter實例中handleURL方法贤笆,將按照中間件注冊的順序回調(diào)中間件蝇棉,而我們可以在中間件中實現(xiàn)風(fēng)控業(yè)務(wù)、認(rèn)證機制芥永、加密驗簽等等篡殷,中間件的實現(xiàn)大家可查看源碼

路由的效率

目前我們實現(xiàn)的路由是一個同步阻塞型的,在處理并發(fā)的時候可能會出現(xiàn)一些問題埋涧,或者是在注冊比較多的route表達(dá)式以后板辽,遍歷和匹配的過程會損耗性能,比較好的實現(xiàn)方式是棘催,將Route修改成異步非阻塞型的劲弦,但是API全部要換成異步API,起步我們先把同步型的搞定醇坝,隨后我們將會參照promise范式來進行異步改造邑跪,提升路由效率。

路由的使用

在大部分App實踐MVVM架構(gòu)或者更為復(fù)雜的VIPER架構(gòu)的時候呼猪,除了迫切需要一個比較解耦的消息傳遞機制画畅,如何更好的剝離目標(biāo)實體的獲取和配合UIKit這一層的轉(zhuǎn)場邏輯是一項比較復(fù)雜的挑戰(zhàn),路由實際上是充當(dāng)MVVM的ViewModel中比較解耦的目標(biāo)獲取邏輯和VIPER中Router層宋距,P與V的調(diào)用全部靠Router轉(zhuǎn)發(fā)夜赵。
在實施以組件化為目的的工程化改造中,如何抽離單獨業(yè)務(wù)為組件乡革,比較好的管理業(yè)務(wù)與業(yè)務(wù)之間的依賴寇僧,就必須使用一個入侵比較小的Route,WLRRoute入侵的地方在于WLRRouteHandler的transitionWithRequest邏輯中沸版,通過一個UIViewController的擴展嘁傀,給 targetViewController.wlr_request = request;設(shè)置了WLRRouteRequest對象給目標(biāo)業(yè)務(wù),但雖然如此视粮,你依舊可以重寫WLRRouteHandler的transitionWithRequest方法细办,來構(gòu)建你自己參數(shù)傳遞方式,這一點完全取決于你如何更好的使得業(yè)務(wù)無感知而使用路由蕾殴。

最后附上代碼地址:
喜歡的來個星吧…
https://github.com/Neojoke/WLRRoute
12.27更新:
感謝這位同學(xué)笑撞,寫了一篇討論性的 文章,對我啟發(fā)很大钓觉,我尊敬以及欣賞能夠深入思考并且愿意分享自己的idea的人茴肥。
2017.02.26更新:
WLRRoute增加了核心邏輯的注釋升級到0.0.9版本,下一個0.1.0版本將加入異步處理荡灾,敬請期待
2017.03.01更新:
更新文章的整體閱讀流暢度

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瓤狐,一起剝皮案震驚了整個濱河市瞬铸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌础锐,老刑警劉巖嗓节,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異皆警,居然都是意外死亡拦宣,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門信姓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛙吏,“玉大人株婴,你說我怎么就攤上這事。” “怎么了华蜒?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵俺夕,是天一觀的道長促王。 經(jīng)常有香客問我瞒大,道長,這世上最難降的妖魔是什么俊性? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任略步,我火速辦了婚禮,結(jié)果婚禮上定页,老公的妹妹穿的比我還像新娘趟薄。我一直安慰自己,他們只是感情好典徊,可當(dāng)我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布杭煎。 她就那樣靜靜地躺著,像睡著了一般卒落。 火紅的嫁衣襯著肌膚如雪羡铲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天儡毕,我揣著相機與錄音也切,去河邊找鬼。 笑死腰湾,一個胖子當(dāng)著我的面吹牛雷恃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播费坊,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼倒槐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了葵萎?” 一聲冷哼從身側(cè)響起导犹,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤唱凯,失蹤者是張志新(化名)和其女友劉穎羡忘,沒想到半個月后谎痢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡卷雕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年节猿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漫雕。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡滨嘱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出浸间,到底是詐尸還是另有隱情太雨,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布魁蒜,位于F島的核電站囊扳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏兜看。R本人自食惡果不足惜锥咸,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望细移。 院中可真熱鬧搏予,春花似錦、人聲如沸弧轧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽精绎。三九已至速缨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捺典,已是汗流浹背鸟廓。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留襟己,地道東北人引谜。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像擎浴,于是被迫代替她去往敵國和親员咽。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,834評論 2 345

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