接上一篇移動端路由層設計,這一篇是實戰(zhàn)篇,手把手的帶你編寫一個簡單的路由組件。有朋友說很多人都收藏以后就再也沒看過,其實這屬于時間管理問題,在你忙碌的工作和生活的時候,有時候需要你稍微停頓一下,思考一下,例如,你可以把本篇文章收藏以后再在iPhone的提醒事項里加入到一個閱讀清單里,不用設置提醒,只需要在你閑的時候抽出一兩個小時,看一下。想象一下你自己動手從發(fā)現(xiàn)問題到解決問題再到做出一個解決問題的組件的過程給你帶來的成就感和獲取的進階經(jīng)驗,再稍微改變一下你對每天需要處理的繁雜事物的管理方式,也許你的生活和工作就會豁然開朗。
這個路由究竟是什么鬼?能解決什么問題?
舉一些場景來看看
場景1:一個App項目中團隊人員比較多,不同的人負責不同的模塊開發(fā),有的人直接使用資源文件設計的,有的人用代碼直接寫的,有的人負責登錄,有的人負責訂單,突然有一天搞訂單的開發(fā)A找搞登錄的開發(fā)B說要調(diào)一下登錄,登錄成功以后你要再回調(diào)下我寫的模塊的方法告訴我成功登錄,我要刷新一下訂單頁面,B傻傻的就答應了,找B的人C、D、F....越來越多,B負責的代碼越寫越多,同時A也不怎么開心,因為A發(fā)現(xiàn)調(diào)B寫的登錄要通過類實例化函數(shù)獲取模塊,調(diào)C寫的支付使用工廠方法,調(diào)D寫的計算器組件又是另外一種寫法,結果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)的問題大家或多或少都會遇見,總結一下就是:
- 因為不同人負責不同模塊,調(diào)用他人必須了解他人編寫的模塊如何調(diào)用,對象是啥,初始化方式是啥,這違背了面向?qū)ο蟮姆庋b原則
- 引入不同的模塊頭文件,多了以后,所依賴的外部發(fā)生一丁點變化你就要跟著變,邏輯變得越來越耦合,不利于維護
- 調(diào)用不同模塊要反復與他人溝通傳參、回調(diào)流程、接口定義等等,溝通效率低下
- 產(chǎn)品提出各種需求,但是我寫的代碼都是差不多的,來一個頁面我需要寫一些相同邏輯的代碼,而且產(chǎn)品還抱怨每次加相同的東西就要改代碼發(fā)版,這顯然不能滿足復用的要求。
總結:
依賴多、耦合高、復用低。
可我們都知道有這么句話啊:高內(nèi)聚、低耦合,職責單一邏輯清晰。
路由就是解決上面的問題
我們已經(jīng)發(fā)現(xiàn)依賴比較大是因為要導入其他模塊的頭文件,了解其他模塊的邏輯和定義,如果多了,你的代碼中引入的頭文件或者導入的包名越來越多,改一下牽一發(fā)而動全身啊。大概是這個樣子:
依賴的問題很嚴重,要想破除這樣的依賴,我們能想到的辦法就是找個調(diào)度中心去做這件事,其實各個業(yè)務模塊并不關心其他模塊具體的業(yè)務邏輯是什么,也不需要知道這個模塊如何獲取,我只關心怎么調(diào)用和反饋的結果,而這個有了調(diào)度中心這個東西,每個模塊不需要依賴其他模塊,只需要調(diào)度中心關心每個模塊的調(diào)度。
有了Route這個調(diào)度中心,每個模塊就不用寫那么多重復的耦合代碼了,也不需要在導入那么多頭文件了和引入那么多包名了,這些藍色的箭頭代表著調(diào)用方式,如果調(diào)用方式再統(tǒng)一一下,溝通效率就提升上去了,因為我們可以用一套約定好的數(shù)據(jù)協(xié)議來代替重復溝通,有時候我們需要靠約定和協(xié)議來提高我們的工作效率。
Tips:
發(fā)現(xiàn)問題這個環(huán)節(jié)很重要,你在工作中經(jīng)常要反復做的,浪費時間的都是需要你去優(yōu)化和花大力氣去解決的,作為一個專業(yè)人士,不斷改進你的代碼,優(yōu)化你的工作流程,帶動團隊向好的協(xié)作方式去轉(zhuǎn)型,這是專業(yè)人士的習慣,更應該成為你的習慣。同時針對代碼存在的問題,也許你經(jīng)常會隱隱約約感到有問題,就是不知道問題在什么地方,那么需要問問自己有沒有以下情況:哪些代碼是經(jīng)常寫且重復度很高的,是不是可以抽象出來?哪些代碼需要反復的變動,是不是可以做成配置或者是定義一套數(shù)據(jù)格式來滿足動態(tài)兼容?有沒有一些現(xiàn)成的設計模式可以解決這些問題?比方說,調(diào)度中心則使用的是中介者模式。我見過
為啥要說iOS路由呢?
路由層其實在邏輯功能上的設計都是一樣的,很多人把App中的視圖切換當做是路由組件的功能職責,這點我持否定態(tài)度,從單一職責角度和MVC框架分析來看,視圖切換屬于View中的交互邏輯并不屬于消息傳遞或者是事件分發(fā)的范疇,但路由請求、視圖轉(zhuǎn)場的實現(xiàn)部分與Android平臺和iOS平臺上的導航機制有著非常緊密的關系,Android操作系統(tǒng)有著天然的架構優(yōu)勢,Intent機制可以協(xié)助應用間的交互與通訊,是對調(diào)用組件和數(shù)據(jù)傳遞的描述,本身這種機制就解除了代碼邏輯和界面之間的依賴關系,只有數(shù)據(jù)依賴。而iOS的界面導航和轉(zhuǎn)場機制則大部分依賴UI組件各自的實現(xiàn),所以如何解決這個問題,iOS端路由的實現(xiàn)則比較有代表性。
其實說白一點,路由層解決的核心問題就是原來界面或者組件之間相互調(diào)用都必須相互依賴,需要導入目標的頭文件、需要清楚目標對象的邏輯,而現(xiàn)在全部都通過路由中轉(zhuǎn),只依賴路由或者某種通訊協(xié)議,或者依靠一些消息傳遞機制連路由都不依賴。其次,路由的核心邏輯就是目標匹配,對于外部調(diào)用的情況來說,URL如何匹配Handler是最為重要的,匹配就必然用到正則表達式。了解這些關鍵點以后就有了設計的目的性,let‘s do it~
總結一下這個路由都要有什么?(需求分析)
我們先根據(jù)上面的模糊的總結梳理一下:
- 路由需要能夠?qū)崿F(xiàn)被其他模塊調(diào)度,從而調(diào)度另外一個模塊
- 接入路由的模塊不需要知道目標模塊的實現(xiàn)
- 調(diào)度發(fā)起方需要有目標的響應回調(diào),類似于http請求,有一個request就要有一個response,才能實現(xiàn)雙向的調(diào)用
- 調(diào)用方式需要統(tǒng)一,統(tǒng)一而松散的調(diào)用協(xié)議和數(shù)據(jù)協(xié)議可以減少大量接入成本和溝通成本
那一個完整的調(diào)度流程應該是這樣的:
看到這個流程以后,可以確定以下幾件事:
- A模塊調(diào)用路由,為表達自己需要調(diào)用的是B模塊,考慮到H5、推送以及其他App的外部調(diào)用,可以使用URL這種方式來定義目標,也就是說用URL來表示目標B
- 對一個URL的請求來說,路由需要有統(tǒng)一的回調(diào)處理,當然,如果不需要回調(diào)也是可以的,回調(diào)是需要目標去觸發(fā)的
- 路由要有處理URL的功能,并調(diào)用其他模塊的能力
根據(jù)以上粗略的定義一下路由的框架:
這里面以供有4部分:
WLRRouter就是一個實體對象,用來提供給其他模塊調(diào)用。
WLRRouteRequest是一個以URL為基礎的實體對象,為什么不直接用URL字符串?因為考慮到如果路由在內(nèi)部調(diào)用其他模塊的時候需要傳入一些原生對象,而URL上只能攜帶類型單一的字符串鍵值對表示參數(shù),所以需要使用這么一個對象進行包裝。
WLRRouteHandler是一個處理某一個WLRRouteRequest請求的對象,當路由接收一個WLRRouteRequest請求,轉(zhuǎn)發(fā)給一個WLRRouteHandler處理,處理完畢以后如果有回調(diào),則回調(diào)給調(diào)用者。URL的請求與Handler的對應關系肯定需要匹配的邏輯,為了使得路由內(nèi)部邏輯更加清晰單獨使用WLRRouteMatcher來處理匹配的邏輯。
深入具體需求,細化功能實現(xiàn)(詳細設計)
有了粗略的需求分析接下來就是細化需求并給出詳細設計的階段了,其實編寫一個模塊要有系統(tǒng)性思維,粗略的需求里面包含了整個模塊要實現(xiàn)的主要核心功能,核心流程是什么,要有哪幾個類才能實現(xiàn)這樣的流程,不要妄圖一下子深入到細枝末節(jié)上,讓細節(jié)左右宏觀上的邏輯架構,大腦不適合同時考慮宏觀和微觀的事情,尤其是對經(jīng)驗不太足的開發(fā)者來說,要逐漸學會大腦在不同的時期進行宏觀和微觀的無縫切換,這樣才能專注目標和結果,在實現(xiàn)過程中再投入全部精力考慮細節(jié),才能保證具體的實現(xiàn)是不偏離總體目標的。
WLRRouteRequest設計
路由層的請求,無論是跨應用的外部調(diào)用(H5調(diào)用、其他App調(diào)用)還是內(nèi)部調(diào)用(內(nèi)部模塊相互調(diào)用),最后都要形成一個路由請求,一個以URL為基礎的request對象,首先需要有攜帶URL,再一個要攜帶請求所需要的參數(shù),參數(shù)有三種,一種是Url上的鍵值對參數(shù),一種是RESTFul風格的Url上的路徑參數(shù),一種是內(nèi)部調(diào)用適用的原生參數(shù),具體是:
這里說一下路徑參數(shù),很多有后端開發(fā)經(jīng)驗的人都知道,一個url上傳遞參數(shù),或者是匹配后端服務的service,Url的路徑對于表達轉(zhuǎn)發(fā)語義十分重要,比方說 :
http://aaaa.com/login
http://aaaa.com/userCenter
那Url中的login和userCenter可以代表是哪個后端服務,那路由就需要設置正則匹配表達式去匹配http://aaaa.com/ 這部分,截取login、userCenter部分,說回我們的路由,App的路由需要通過設置Url的正則表達式來獲取路徑參數(shù),同時我們必須知道這些參數(shù)的值和名稱,那么我可以這樣定義Url匹配的表達式
scheme://host/path/:name([a-zA-Z_-]+)
熟悉正則表達式的孩子都知道分組模式,path后name是key,([a-zA-Z_-]+)是規(guī)定name對應的value應該是什么格式的。那么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ù),比方說要傳給目標UIImage對象,NSArray對象等等
@property (nonatomic, copy, readonly) NSDictionary *primitiveParams;
//目標預留的callBack block,當完成處理以后,回到此Block,完成調(diào)用者的回調(diào)
@property(nonatomic,copy)void(^targetCallBack)(NSError *error,id responseObject);
//是否消費掉,一個request只能處理一次,該字段反應request是否被處理過
@property(nonatomic)BOOL isConsumed;
WLRRouteHandler設計
handler對象要接收一個WLRRouteRequest對象來啟動處理流程,前面經(jīng)過我們的分析,這個handler應該擔負起通過url和參數(shù)獲取目標對象的職責,在一般的route處理中,目標往往是一個視圖控制器,先實現(xiàn)這樣一個通過url調(diào)用某一個視圖控制器的并跳轉(zhuǎn)處理的handler,那么應該是如下的:
handler處理一個request請求是一個具有過程性的邏輯,WLRRouteHandler要作為一個基類,我們知道,這個handler在需要處理獲取目標視圖控制器->參數(shù)傳遞給目標視圖控制器->視圖控制器的轉(zhuǎn)場->完成回調(diào),那么我們需要設計這樣的接口
//即將開始處理request請求,返回值決定是否要繼續(xù)相應request
- (BOOL)shouldHandleWithRequest:(WLRRouteRequest *)request;
//開始處理request請求
-(BOOL)handleRequest:(WLRRouteRequest *)request error:(NSError *__autoreleasing *)error;
// 根據(jù)request獲取目標控制器
-(UIViewController *)targetViewControllerWithRequest:(WLRRouteRequest *)request;
//轉(zhuǎn)場一定是從一個視圖控制器跳轉(zhuǎn)到另外一個視圖控制器,該方法用以獲取轉(zhuǎn)場中的源視圖控制器
-(UIViewController *)sourceViewControllerForTransitionWithRequest:(WLRRouteRequest *)request;
//改方法內(nèi)根據(jù)request、獲取的目標和源視圖控制器,完成轉(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設計
一個matcher應該具有根據(jù)url和參數(shù)判斷是否匹配某個url表達式的邏輯
matcher對象必須擁有url的匹配表達式,類似于 scheme://host/path/:name([a-zA-Z_-]+) ,也有擁有該表達式真正的正則表達式,^scheme://host/path/([a-zA-Z_-]+)$
@interface WLRRouteMatcher : NSObject
//url匹配表達式
@property(nonatomic,copy)NSString * routeExpressionPattern;
//url匹配的正則表達式
@property(nonatomic,copy)NSString * originalRouteExpression;
+(instancetype)matcherWithRouteExpression:(NSString *)expression;
-(WLRRouteRequest *)createRequestWithURL:(NSURL *)URL primitiveParameters:(NSDictionary *)primitiveParameters targetCallBack:(void(^)(NSError *, id responseObject))targetCallBack;
設計-(WLRRouteRequest *)createRequestWithURL:(NSURL *)URL primitiveParameters:(NSDictionary *)primitiveParameters targetCallBack:(void(^)(NSError *, id responseObject))targetCallBack;
這個方法,可以通過傳入url和參數(shù),檢查是否返回request請求,來表示該WLRRouteMatcher對象所擁有的匹配表達式與url是否能夠匹配,這句話有點繞,看不懂的多看幾遍。
WLRRouter
WLRRouter是路由實體對象,后端開發(fā)者對于路由掛載的概念非常了解,其實這樣一個路由實體對象可以完成對URL的攔截和處理并返回結果,事實上,根據(jù)前面的梳理和總結,WLRRouter對象內(nèi)部應該保存了需要匹配攔截的URL表達式,而前面我們知道Url的匹配表達式是存儲在WLRRouteMatcher對象中的,并且一個Url傳入檢查是否匹配也是Matcher對象提供的功能,對于匹配上的Url需要有對應的Handler處理,所以Router對象的內(nèi)部存在Machter對象和Handler對象一一對應的關系,并且擁有注冊Url表達式對應到Handler的功能,也具有傳入Url和參數(shù)就能匹配到Handler的功能,還要有一個檢測Url是否能有對應Handler處理的功能,所以應該是:
這里有兩種注冊的方法,注冊handler的就不需再多描述,另外一個是注冊Block的回調(diào)形式,因為有時候可能會需要一些簡單的Url攔截,去做一些事情,這里面的Block需要返回一個request對象,這是因為,如果Block沒有對request的回調(diào)做處理,Router應該處理調(diào)用者的回調(diào)問題,否則就會出現(xiàn)調(diào)用者設置了回調(diào)的Block而沒有人調(diào)用回來,這樣就尷尬了。
/**
注冊一個route表達式并與一個block處理相關聯(lián)
@param routeHandlerBlock block用以處理匹配route表達式的url的請求
@param route url的路由表達式,支持正則表達式的分組,例如app://login/:phone({0,9+})是一個表達式,:phone代表該路徑值對應的key,可以在WLRRouteRequest對象中的routeParameters中獲取
*/
-(void)registerBlock:(WLRRouteRequest *(^)(WLRRouteRequest * request))routeHandlerBlock forRoute:(NSString *)route;
/**
注冊一個route表達式并與一個block處理相關聯(lián)
@param routeHandlerBlock handler對象用以處理匹配route表達式的url的請求
@param route url的路由表達式,支持正則表達式的分組,例如app://login/:phone({0,9+})是一個表達式,:phone代表該路徑值對應的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 傳給目標對象的回調(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;
梳理總結:
從以上我們規(guī)劃的幾個類的接口,我們可以清楚的看到Router工作的流程。
- 首先實例化Router對象
- 實例化Handler或者是Block,通過Router的注冊接口使得一個Url的匹配表達式對應一個Handler或者是一個block
- Router內(nèi)部會將Url的表達式形成一個Matcher對象進行保存,對應的Handler或處理的Block會與Matcher一一對應,怎么對應呢?應該使用路由表達式進行關聯(lián)
- Router通過handle方法,接收一個Url的請求,內(nèi)部遍歷所有的Matcher對象,將Url和參數(shù)轉(zhuǎn)換為Request對象,如果能轉(zhuǎn)換為Request對象則說明能匹配,如果不能則說明該Url不能被路由實體處理
- 拿到Request對象以后,則根據(jù)Matcher對應的路由表達式找到對應的Handler或者是Block
- 根據(jù)Handler的幾個關鍵方法,傳入Request對象,按照順序完成處理邏輯的觸發(fā),最后如果有request當中包含有目標的回調(diào),則將處理結果通過回調(diào)的Block響應給調(diào)用方
- Handler完成處理后,Router完成本次路由請求
Tips:
很多開發(fā)者把敏捷開發(fā)當做來了需求不管三七二十一,一把梭子就是干,不斷寫不斷改。??其實敏捷開發(fā)是一種模式,并不簡單是快速迭代的意思。初入行的程序員其實都是coder(編碼員),基本上在靠模仿代碼和代碼套路去工作,真正的Programmer(程序設計師)是在設計代碼,科班出身的程序員往往在進階過程中突然發(fā)現(xiàn)大學里面的軟件工程有多么重要,其實設計能力的培養(yǎng)需要有一個正規(guī)的流程,就像本教程的大綱一樣,發(fā)現(xiàn)問題->需求分析->總體設計->具體實現(xiàn)->測試->發(fā)布維護,有了清晰的流程,把你的精力和時間按照不同的階段進行分配和投入,你就會豁然開朗,比方說在總體設計過程中,就需要你以宏觀的功能流程去考慮大體的模塊有幾個,模塊的關系是怎樣,每個模塊的核心職責是什么,建議根據(jù)需求去畫一個邏輯流程圖,將每個邏輯分支都補全,再根據(jù)流程圖規(guī)劃總體框架,總體框架通過類圖來表達,每個類的屬性和行為都確定以后,再進入具體設計階段就非常輕松和容易了,同時類圖畫完,技術方案的可行性和實現(xiàn)所需時間也就非常容易精確評估了
給架子填充骨血(具體實現(xiàn)):
有了上面大體上的架子我們就能相信只要按照這個架子,就能完成你在需求分析階段規(guī)劃的功能目標,現(xiàn)在我們要做的就是在我們設計的這個牛逼的框架里填充血肉,去實現(xiàn)它,這部分是一個非常有意思的過程,在上一步你已經(jīng)獲得了相當大的信心,在這一步你只需要按照規(guī)定盡力去實現(xiàn),在有信心的情況下,你會思維活躍,因為你明確了要實現(xiàn)何種功能的目標,大腦會自動根據(jù)目標和現(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表達式,比方說調(diào)用登錄界面的表達式可以為:AppScheme://user/login/138********,那URL的匹配表達式可以是:/login/:phone([0-9]+),路徑必須以/login開頭,后面接0-9的電話號碼數(shù)字,當然你也可以直接把電話號碼的正則匹配寫全
@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;
//目標的viewcontrolller或者是組件可以通過這個
@property(nonatomic,copy)void(^targetCallBack)(NSError *error,id responseObject);
//用以表明該request是否被消費
@property(nonatomic)BOOL isConsumed;
//簡便方法,用以下標法取參數(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;
//默認完成目標的回調(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)用方設置傳入callBack的時候,因為request是消費型的,所以將TargetCallBack重新包裝,在回調(diào)的時候需要將isConsumed屬性設置為YES,表示該request已經(jīng)被處理消耗,這里實現(xiàn)的比較簡單,其實request是應該具有狀態(tài)的,比方說未處理,處理中,已處理,實現(xiàn)一個優(yōu)雅的狀態(tài)機會更好的表達邏輯:
-(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);
};
}
默認的回調(diào)方法是為了處理響應者沒有觸發(fā)回調(diào),則需要有默認的回調(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)用的目標視圖控制器
-(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
當WLRRouter對象完成了URL的匹配生成Request,并尋找到Handler的時候,首先會調(diào)用
-(BOOL)shouldHandleWithRequest:(WLRRouteRequest *)request{
return YES;
}
子類可以覆寫這個方法,實現(xiàn)一些參數(shù)檢查或者當然App狀態(tài)檢查的工作,比方說檢測當前界面是否已經(jīng)有模態(tài)跳轉(zhuǎn)覆蓋的界面或者是彈窗之類,做一些清場工作來為本次轉(zhuǎ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這一句,在獲取目標控制器以后,我們會將request對象賦值給目標控制器,這是一種正向賦值的做法,為了降低接入成本,創(chuàng)建一個UIViewController的擴展,添加wlr_request這么一個屬性,這么做也是不得已為之,因為UIKit庫中ViewController并沒有上下文機制,也就不能通過無入侵的形式進行消息傳遞。
在此我們準備好轉(zhuǎn)場所需要的一切,包括是否模態(tài)跳轉(zhuǎn),目標控制器,源控制器,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里面寫上獲取目標控制器的方法,會造成繼承很多很多handler,實際上你可以發(fā)揮一下想象力,在targetViewControllerWithRequest方法里,你已經(jīng)能拿到request,而request里面有路由表達式,實際上一個路由表達式應該對應一個或者多個視圖控制器,你完全可以做一個配置文件,在targetViewControllerWithRequest方法里通過路由表達式來獲取目標視圖控制器的初始化信息,目標控制器的初始化無非分為直接代碼初始化,要么是storyboard初始化,要么是xibs初始化,但是這個配置文件如何設計和使用,不在本文討論范疇,需要你開動腦筋去解決。
同樣preferModalPresentationWithRequest也可以根據(jù)配置來處理是否要進行模態(tài)跳轉(zhuǎn)的問題。transitionWithRequest方法里面根據(jù)source來進行導航push或者是模態(tài)跳轉(zhuǎn),你大可以繼承一下,然后重寫轉(zhuǎn)場過程。
Handler類里面的生命周期函數(shù)是不是很像UIViewControllerContextTransitioning轉(zhuǎn)場上下文的協(xié)議的設定?- (nullable __kindof UIViewController *)viewControllerForKey:(UITransitionContextViewControllerKey)key;
方法使上下文提供目標控制器和源控制器,其實在handler中你完全可以自定義一個子類,在transitionWithRequest方法里,設置遵守UIViewControllerTransitioningDelegate的代理,然后在此提供遵守 UIViewControllerAnimatedTransitioning的動畫控制器,然后自定義轉(zhuǎn)場上下文,實現(xiàn)自定義UI轉(zhuǎn)場,而對應的匹配邏輯是與此無關的,我們就可以在路由曾控制全局的頁面轉(zhuǎn)場效果。對自定義轉(zhuǎn)場不太熟悉的同學請移步我之前的文章:
ContainerViewController的ViewController 轉(zhuǎn)場
WLRRouteMatcher
#import <Foundation/Foundation.h>
@class WLRRouteRequest;
@interface WLRRouteMatcher : NSObject
//傳入URL匹配的表達式,獲取一個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對象,說到匹配就一定不會傻傻的用字符串相等,肯定是要借助正則表達式來判斷是否匹配和將分組的內(nèi)容從字符串中取出,我們初始化Macter對象的時候要分配一個Url表達式給它,格式前面提到過,Url諸如http://abc.com/:name([a-zA-Z0-9-]+) 的格式并不是真正的標準正則表達式,如果要把name取出,則必須將其裝換為標準的正則表達式像:^http://abc.com/([a-zA-Z0-9-]+)$ 這樣,那么就需要進行轉(zhuǎn)換和提取:后面的key值,在Fundaction庫中,NSRegularExpression是用來處理正則表達式的,為了使邏輯更加清晰,我們可以把這樣的邏輯通過繼承NSRegularExpression封裝到一個叫WLRRegularExpression的類中,這個類提供匹配和轉(zhuǎn)換標準正則表達式的功能如下:
匹配的結果我們可以通過一個WLRMatchResult來表示。傳入路由url的匹配表達式穿實話WLRRegularExpression實例,在初始化的時候就轉(zhuǎn)換為標準的正則表達式和將:后面的key的名字保存到routerParamNamesArr當中,matchResultForString接口可接受一個Url從而生成匹配結果的對象,匹配結果的對象里存儲著路徑參數(shù)信息和是否匹配的結果。
#import <Foundation/Foundation.h>
@class WLRMatchResult;
@interface WLRRegularExpression : NSRegularExpression
//傳入一個URL返回一個匹配結果
-(WLRMatchResult *)matchResultForString:(NSString *)string;
//根據(jù)一個URL的表達式創(chuàng)建一個WLRRegularExpression實例
+(WLRRegularExpression *)expressionWithPattern:(NSString *)pattern;
@end
WLRRegularExpression繼承NSRegularExpression
-(instancetype)initWithPattern:(NSString *)pattern options:(NSRegularExpressionOptions)options error:(NSError * _Nullable __autoreleasing *)error{
//初始化方法中將URL匹配的表達式pattern轉(zhuǎn)換為真正的正則表達式
NSString *transformedPattern = [WLRRegularExpression transfromFromPattern:pattern];
//用轉(zhuǎn)化后的結果初始化父類
if (self = [super initWithPattern:transformedPattern options:options error:error]) {
//同時將需要提取的子串的值的Key保存到數(shù)組中
self.routerParamNamesArr = [[self class] routeParamNamesFromPattern:pattern];
}
return self;
}
//轉(zhuǎn)換為正則表達式
+(NSString*)transfromFromPattern:(NSString *)pattern{
//將pattern拷貝
NSString * transfromedPattern = [NSString stringWithString:pattern];
//利用:[a-zA-Z0-9-_][^/]+這個正則表達式,將URL匹配的表達式的子串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-_]+這個正則表達式,將帶有提取子串的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];
}
//最后結尾要用$符號
transfromedPattern = [transfromedPattern stringByAppendingString:@"$"];
//最后會將/login/:phone([0-9]+)轉(zhuǎn)換為login/([0-9]+)$
return transfromedPattern;
}
在Matcher對象匹配一個URL的時候
-(WLRMatchResult *)matchResultForString:(NSString *)string{
//首先通過自身方法將URL進行匹配得出NSTextCheckingResult結果的數(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結果
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;
//匹配的表達式
@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部分當做URL匹配表達式生成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結果,看是否匹配
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;
}
告一段落,這部分其實算是路由組件中比較核心的邏輯,為什么要拆成三個類,大家思考一下分割邏輯的思想,職責單一以后,你才會聚焦目的本身。
WLRRouter
完成了以上幾個類的實現(xiàn),WLRRouter就比較容易了。
@class WLRRouteRequest;
@class WLRRouteHandler;
@interface WLRRouter : NSObject
//注冊block回調(diào)的URL匹配表達式,可用作內(nèi)部調(diào)用
-(void)registerBlock:(WLRRouteRequest *(^)(WLRRouteRequest * request))routeHandlerBlock forRoute:(NSString *)route;
//注冊一個WLRRouteHandler對應的URL匹配表達式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)部分,有三個屬性,這三個屬性用來設置matcher、handler、block的對應關系,key都為路由表達式:
//每一個URL的匹配表達式對應一個matcher實例,放在字典中
@property(nonatomic,strong)NSMutableDictionary * routeMatchers;
//每一個URL匹配表達式route對應一個WLRRouteHandler實例
@property(nonatomic,strong)NSMutableDictionary * routeHandles;
//每一個URL匹配表達式route對應一個回調(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對應的handler對象
[self.routeHandles removeObjectForKey:route];
//將routeHandlerBlock和route存入對應關系的字典中
self.routeblocks[route] = routeHandlerBlock;
}
}
-(void)registerHandler:(WLRRouteHandler *)handler forRoute:(NSString *)route{
if (handler && [route length]) {
//首先生成route對應的WLRRouteMatcher實例
[self.routeMatchers setObject:[WLRRouteMatcher matcherWithRouteExpression:route] forKey:route];
//刪除route對應的block回調(diào)
[self.routeblocks removeObjectForKey:route];
//設置route對應的handler
self.routeHandles[route] = handler;
}
}
接下來完善handle方法,設置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對應的回調(diào)block或者是handler實例
if ([handler isKindOfClass:NSClassFromString(@"NSBlock")]) {
WLRRouteRequest *(^blcok)(WLRRouteRequest *) = handler;
//調(diào)用回調(diào)的block
WLRRouteRequest * backRequest = blcok(request);
//判斷block里面是否消費了此request,如果沒有而目標設置了目標回調(diào)targetCallBack,那么在此進行默認回調(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對應的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對象中,將匹配后的結果生成WLRRouteRequest實例以攜帶足夠完整的數(shù)據(jù),同時將真正處理視圖控制器的轉(zhuǎn)場或者是組件的加載或者是未來可能拓展的handler業(yè)務封裝到WLRRouteHandler實例中,匹配邏輯對應的處理邏輯干凈分離,Matcher對象將匹配邏輯單獨封裝,如果有一天需要增加多種URL的匹配邏輯,則可以更換Matcher或者添加Matcher就可以,處理邏輯可以通過繼承擴展或者沖洗WLRRouteHandler的生命周期函數(shù)來更好的處理回調(diào)業(yè)務。如果WLRRouteHandler不能提供足夠多的擴展性,則可以使用block回調(diào)最大限度的進行擴展。
以上,就是路由部分的整體實現(xiàn)。
Tips:
在具體的實現(xiàn)的過程中,不要被總體設計給套死,前面設計了幾個類,設計了幾個層次以后,就畫地為牢,在實現(xiàn)具體細節(jié)的時候仍可以繼續(xù)進行局部設計,分割邏輯,控制代碼復雜度,一方面你要注重在局部進行進一步設計和分割的時候帶來的究竟是什么,可讀性?可維護性?還是幫助你能夠進行思考?有時候,注釋可能更加能給你的邏輯帶來清晰的表述,畢竟幾千行的代碼很正常,如果沒有經(jīng)驗,可能邏輯重點記得不太清楚,那就需要動動筆頭好好記錄一下,再進一步實現(xiàn),將復雜邏輯進行適當分割,是一種常見的策略
最終的路由整體類圖和如何設計代碼的總結:
整體的設計就是如此,我們走了一趟從發(fā)現(xiàn)問題到最后通過封裝一個路由組件解決問題的過程,在這里,對于新手程序員我想討論一些有意義的東西,也就是手把手寫出一個路由的過程中,思路從何而來?代碼設計怎么從無到有?對于一個邏輯問題怎么轉(zhuǎn)換為代碼實踐?授之以魚不如授之以漁,在這個過程中我們總結一下哪些是比較重要的:
- 對于經(jīng)常重復的工作和低效的工作流程有沒有敏銳的察覺,是否有想解決的愿望,畢竟能解決了你能節(jié)省更多時間,何樂而不為?可有些人寧愿加班做一些簡單的重復工作也不希望直面挑戰(zhàn)創(chuàng)造一些解決問題的新方式
- 在思考問題的解決方案的時候,大腦有沒有將問題抽象一下去尋找有沒有相應解決問題的模式、方法論或者是方案?(比如說發(fā)現(xiàn)路由方案,發(fā)現(xiàn)中介者模式,發(fā)現(xiàn)分割思想和職責單一,發(fā)現(xiàn)軟件工程的流程)
- 天賦并不是最重要的,甚至天賦是一種結論而不是條件,大腦不擅長同時處理很多事情,在你設計或者編碼的過程中,通過標準化流程能最大程度的提高大腦思考的效率,比方說,總結問題就是總結問題而不要變總結變思考解決方案,否則會片面或者影響問題的客觀描述,總結問題->需求分析->總體設計->具體實現(xiàn),這就是一個簡單的大腦思考流程,如果想不清楚,通過思維導圖、流程圖、UML建模等等,去把大腦需要同時照顧到的東西呈現(xiàn)在眼前,你只要去對比對照,一步步來就可以了,并不是有人特別有天賦,而是有人比你有方法
- 發(fā)散很重要,每當你從出現(xiàn)問題到解決問題以后,你可以安心的天馬行空的頭腦風暴,在這個過程中你可能對解決問題的方式進行重新審視和回顧,發(fā)現(xiàn)不足,甚至可以對其擴展使其能解決更多問題,就比如你會考慮路由的效率、安全,從而誕生中間件、異步事件的想法,這點很重要,但卻很簡單
- 自我引導能力,看了文章一步步做了出來,其實就跟做菜一樣,你學會了一道菜該怎么做,但為什么別人能做,或者說別人從無到有的過程經(jīng)歷了什么,你為什么沒想到,這種想法會激勵你自我引導,從而提升自己,進一步改變自己,如果你從來沒有對他人為什么能做到感到好奇,那就說明你的自我意識和自我引導力不夠強,需要停下每天寫代碼的手,認真思考一下人生了
路由的安全
有兩個方面可以去做
- WLRRouteHandler實例中,
-(BOOL)shouldHandleWithRequest:(WLRRouteRequest *)request
中可以檢測request中的參數(shù),比方說效驗source或者是效驗業(yè)務參數(shù)完整等 - WLRRouter實例中handleURL方法,將按照中間件注冊的順序回調(diào)中間件,而我們可以在中間件中實現(xiàn)風控業(yè)務、認證機制、加密驗簽等等,中間件的實現(xiàn)大家可查看源碼
路由的效率
目前我們實現(xiàn)的路由是一個同步阻塞型的,在處理并發(fā)的時候可能會出現(xiàn)一些問題,或者是在注冊比較多的route表達式以后,遍歷和匹配的過程會損耗性能,比較好的實現(xiàn)方式是,將Route修改成異步非阻塞型的,但是API全部要換成異步API,起步我們先把同步型的搞定,隨后我們將會參照promise范式來進行異步改造,提升路由效率。
路由的使用
在大部分App實踐MVVM架構或者更為復雜的VIPER架構的時候,除了迫切需要一個比較解耦的消息傳遞機制,如何更好的剝離目標實體的獲取和配合UIKit這一層的轉(zhuǎn)場邏輯是一項比較復雜的挑戰(zhàn),路由實際上是充當MVVM的ViewModel中比較解耦的目標獲取邏輯和VIPER中Router層,P與V的調(diào)用全部靠Router轉(zhuǎn)發(fā)。
在實施以組件化為目的的工程化改造中,如何抽離單獨業(yè)務為組件,比較好的管理業(yè)務與業(yè)務之間的依賴,就必須使用一個入侵比較小的Route,WLRRoute入侵的地方在于WLRRouteHandler的transitionWithRequest邏輯中,通過一個UIViewController的擴展,給 targetViewController.wlr_request = request;設置了WLRRouteRequest對象給目標業(yè)務,但雖然如此,你依舊可以重寫WLRRouteHandler的transitionWithRequest方法,來構建你自己參數(shù)傳遞方式,這一點完全取決于你如何更好的使得業(yè)務無感知而使用路由。
最后附上代碼地址:
喜歡的來個星吧…
https://github.com/Neojoke/WLRRoute
12.27更新:
感謝這位同學,寫了一篇討論性的 文章,對我啟發(fā)很大,我尊敬以及欣賞能夠深入思考并且愿意分享自己的idea的人。
2017.02.26更新:
WLRRoute增加了核心邏輯的注釋升級到0.0.9版本,下一個0.1.0版本將加入異步處理,敬請期待
2017.03.01更新:
更新文章的整體閱讀流暢度