一步步構建 iOS 路由

發表於2016-12-24

接上一篇移動端路由層設計
為啥要說iOS路由呢?
路由層其實在邏輯上的設計都是一樣的,關於對介面跳轉的實現部分卻與Android平臺和iOS平臺上的導航機制有著非常緊密的關係,Android作業系統有著天然的架構優勢,Intent機制可以協助應用間的互動與通訊,是對呼叫元件和資料傳遞的描述,本身這種機制就解除了程式碼邏輯和介面之間的依賴關係,只有資料依賴。而iOS的介面導航和轉場機制則大部分依賴UI元件各自的實現,所以如何解決這個問題,iOS端路由的實現則比較有代表性。
其實說白一點,路由層解決的核心問題就是原來介面或者元件之間相互呼叫都必須相互依賴,需要匯入目標的標頭檔案、需要清楚目標物件的邏輯,而現在全部都通過路由中轉,只依賴路由,或者依靠一些訊息傳遞機制連路由都不依賴。其次,路由的核心邏輯就是目標匹配,對於外部呼叫的情況來說,URL如何匹配Handler是最為重要的,匹配就必然用到正規表示式。瞭解這些關鍵點以後就有了設計的目的性,let‘s do it~

設計類圖:

1124274-e05a8d382f2841e5
RouteClassMap.png

這裡面有如下幾個類:

  1. WLRRouteRequest,路由層的請求,無論是跨應用的外部呼叫還是內部呼叫,最後都形成一個路由請求,該請求包含了URL上的queryparameters和路徑引數,還有內部呼叫時直接傳入的原生引數,還有請求發起者對目標預留的回撥block
  2. WLRRouteHandler,路由層的handler處理,handler接收一個WLRRouteRequest物件,來完成是否是介面跳轉,還是元件載入,還是內部邏輯
  3. WLRRouter,路由核心物件,內部持有註冊的Handler,比方說負責介面跳轉的Handler,負責元件載入的Handler,負責API的Handler等等,路由的作用就是將外部呼叫傳入的URL或者是內部呼叫傳入的target,在內部匹配上對應的handler,然後呼叫生命週期方法,完成處理過程,當然,圖中還有route的中介軟體,實際上是預留AOP的口子,方面後期擴充套件
  4. WLRRouteMatcher,用以處理外部呼叫的URL是否能與預設的正規表示式匹配,在WLRRouter中,每一次註冊一個handler,都會用一個URL匹配的表示式生成一個WLRRouteMatcher
  5. WLRRegularExpression,繼承NSRegularExpression,用以匹配URL,WLRRouteMatcher內部有一個WLRRegularExpression物件,WLRRouteMatcher接受一個URL,會使用WLRRegularExpression生成一個WLRMatchResult物件,來確定是否匹配成功,如果匹配成果則將URL上的路徑引數給取出來
  6. WLRMatchResult,用以描述WLRRegularExpression的匹配結果,包含路徑引數

工作流程:

  1. App啟動例項化WLRRouter物件
  2. 例項化WLRRouteHandler物件
  3. WLRRouter物件掛載WLRRouteHandler例項與URL的表示式相對應,WLRRouter內部生成一個WLRRouteMatcher物件,與URL的表示式相對應
  4. 外部呼叫的URL和callback傳入WLRRouter物件
  5. WLRRouter物件遍歷內部持有的URL的匹配表示式,並找到每一個WLRRouteMatcher物件,將URL傳入看是否能返回WLRRouteRequest物件
  6. 將WLRRouteRequest物件傳入對應的WLRRouteHandler物件
  7. WLRRouteHandler物件根據WLRRouteRequest尋找到TargetViewController和SourceViewController,在生命週期函式裡,完成引數傳遞與檢視轉場

WLRRouteRequest:

瞭解了以上,我們從WLRRouteRequest入手。
其實WLRRouteRequest跟NSURLRequest差不多,不過WLRRouteRequest繼承NSObject,實現NSCopying協議,大概如下:

NSURLRequest其實應該是個值型別的物件,所以實現拷貝協議,該物件的實現部分沒有什麼可講的,對照原始碼查閱即可。

WLRRouteHandler

當WLRRouter物件完成了URL的匹配生成Request,並尋找到Handler的時候,首先會呼叫- (BOOL)shouldHandleWithRequest:(WLRRouteRequest *)request;,來確定handler是否願意處理,如果願意,則呼叫-(BOOL)transitionWithRequest:(WLRRouteRequest *)request error:(NSError *__autoreleasing *)error;,內部則通過便利方法獲取targetViewController與SourceViewController,然後進行轉場,核心方法的實現為:

這裡根據SourceController的型別進行判斷,其實request物件的資訊足夠可以判斷目標檢視應該如何開啟,從本質上來講,URL的匹配表示式是跟業務強關聯的也是跟UI互動邏輯強關聯的,transitionWithRequest方法實現裡,你大可以繼承一下,然後重寫轉場過程,甚至你可以在這自己設定iOS7自定義的轉場,提供動畫控制器和實現轉場協議的物件,進而可以整體的控制Appp內部的實現。

WLRRegularExpression

該類繼承NSRegularExpression

該物件主要的功能是將一個URL傳入檢視是否匹配,並且將表示式上宣告的路徑引數從URL上取下來。
比說,我們設定的URL匹配的表示式是: login/:phone([0-9]+),那AppScheme://user/login/138** 這樣的URL應該是匹配,並且將138的手機號取出來,對應到phone上,這個過程必須用到正規表示式的分組提取子串的功能,:phone是約定好的提取子串的值對應的key的名字,其實這個url的正規表示式應該是: /login/([0-9]+)$,那麼WLRRegularExpression物件需要知道需要提取所有子串的key還有將URL匹配的表示式轉換為真正的正規表示式。

在Matcher物件匹配一個URL的時候

核心程式碼總共80多行,原始碼大家可以詳閱

WLRRouteMatcher

屬性有如下:

初始化方法:

匹配方法:

WLRRouter

在實現部分,有三個屬性:

在Route掛在Handler和回撥的block的時候:

接下來完善handle方法:

以上我們可以看到,Route將匹配的邏輯單獨封裝到WLRRouteMatcher物件中,將匹配後的結果生成WLRRouteRequest例項以攜帶足夠完整的資料,同時將真正處理檢視控制器的轉場或者是元件的載入或者是未來可能擴充的handle業務封裝到WLRRouteHandler例項中,匹配邏輯對應的處理邏輯乾淨分離,匹配邏輯可單獨塑造業務匹配,處理邏輯可以通過繼承擴充套件或者沖洗WLRRouteHandler的生命週期函式來更好的處理回撥業務。如果WLRRouteHandler不能提供足夠多的擴充套件性,則可以使用block回撥最大限度的進行擴充套件。
以上,就是路由部分的整體實現。

轉場的擴充套件

在WLRRouteHandler中,其實我們可以單獨控制路由經過的頁面跳轉的轉場。

這樣的生命週期函式是不是很像UIViewControllerContextTransitioning轉場上下文的協議的設定?- (nullable __kindof UIViewController *)viewControllerForKey:(UITransitionContextViewControllerKey)key;方法使上下文提供目標控制器和源控制器,其實在handler中你完全可以自定義一個子類,在transitionWithRequest方法裡,設定遵守UIViewControllerTransitioningDelegate的代理,然後在此提供遵守 UIViewControllerAnimatedTransitioning的動畫控制器,然後自定義轉場上下文,實現自定義UI轉場,而對應的匹配邏輯是與此無關的,我們就可以在路由曾控制全域性的頁面轉場效果。對自定義轉場不太熟悉的同學請移步我之前的文章:
ContainerViewController的ViewController 轉場

路由的安全

有兩個方面可以去做

  1. WLRRouteHandler例項中, -(BOOL)shouldHandleWithRequest:(WLRRouteRequest *)request中可以檢測request中的引數,比方說效驗source或者是效驗業務引數完整等
  2. WLRRouter例項中handleURL方法,將在隨後的WLRRoute的0.0.2版本中加入中介軟體的支援,就是在找到handler之前,將按照中介軟體註冊的順序回撥中介軟體,而我們可以在中介軟體中實現風控業務、認證機制、加密驗籤等等

    路由的效率

    目前我們實現的路由是一個同步阻塞型的,在處理併發的時候可能會出現一些問題,或者是在註冊比較多的route表示式以後,遍歷和匹配的過程會損耗效能,比較好的實現方式是,將Route修改成非同步非阻塞型的,但是API全部要換成非同步API,起步我們先把同步型的搞定,隨後慢慢提供非同步版本的路由~

    路由的使用

    在大部分App實踐MVVM架構或者更為複雜的VIPER架構的時候,除了迫切需要一個比較解耦的訊息傳遞機制,如何更好的剝離目標實體的獲取和配合UIKit這一層的轉場邏輯是一項比較複雜的挑戰,路由實際上是充當MVVM的ViewModel中比較解耦的目標獲取邏輯和VIPER中Router層,P與V的呼叫全部靠Router轉發。
    在實施以元件化為目的的工程化改造中,如何抽離單獨業務為元件,比較好的管理業務與業務之間的依賴,就必須使用一個入侵比較小的Route,WLRRoute入侵的地方在於WLRRouteHandler的transitionWithRequest邏輯中,通過一個UIViewController的擴充套件,給 targetViewController.wlr_request = request;設定了WLRRouteRequest物件給目標業務,但雖然如此,你依舊可以重寫WLRRouteHandler的transitionWithRequest方法,來構建你自己引數傳遞方式,這一點完全取決於你如何更好的使得業務無感知而使用路由。

最後附上程式碼地址:
喜歡的來個星吧…
https://github.com/Neojoke/WLRRoute

相關文章