iOS router 設計

小幽幽要乖乖寫程式碼發表於2016-11-22

iOS頁面之間的跳轉常用基於URL的router和mediator,過去有蘑菇街的方案 ([limboy.me/tech/2016/0…]) 和Casa Taloyum的方案 ([casatwy.com/iOS-Moduliz…]) 的激烈討論,業界的各種方案其實也是這兩種方案的變種,包括豆瓣的解決方案 ([github.com/douban/FRDI…]) 是基於URL註冊的擴充套件,吸納了Android的設計,解決了URL字串不能傳遞物件的缺點。
我認為一個好的頁面跳轉方案必須考慮到以下功能:

頁面的解耦

不同頁面之間直接持有viewcontroller物件然後進行push顯然是不好的實踐,A頁面的改動會影響到持有它的B頁面的程式碼,所以通過一箇中間者進行解耦是自然而然的設計。這個中間者可以是一個mediator也可以是一個協議名稱。
把頁面的class名字作為協議名稱是bad case的設計,有時候會看到這樣的設計:

+ (UIViewController *)getControllerWithName:(NSString *)vcClassString action:(NSString *)actionName passObject:(id)object {
             // return viewController;
  }複製程式碼

之所以不是好的設計,是因為A頁面都感知B頁面的類名了,就破壞了解耦的意圖,如果B頁面想重新命名,還需要通知各個引用到B頁面名字的人一一改名字。

傳遞引數

A頁面開啟B頁面,A頁面經常需要向B頁面傳遞引數,B頁面也有可能向A頁面回傳引數。引數必須支援原始資料型別、字串還有不方便序列化的物件(如UIImage)。

遠端跳轉

支援遠端跳轉到指定的頁面是router必不可少的功能。所以router要有把url解析成相應頁面的能力。對於hybrid架構的H5頁面,router也要提供根據url跳轉到H5頁面的功能。

安全性

對於支付等涉及到安全性的頁面,不應該開放遠端跳轉的能力。所以router要區分出遠端調轉和本地跳轉,而且對於遠端跳轉的頁面要限制跳轉的目標頁面範圍。

多級頁面跳轉

提供跳轉到棧底的某個頁面的功能,因為互動和產品的需求,很多頁面並不是直接pop出堆疊,而是使用者點選後要跳轉到棧底的某個特定頁面,所以這時候就要求router不能僅僅提供pop的功能,而是需要利用中間者進行跳轉,而跳轉方案同樣要符合解耦的要求,不能直接引用class或者classname。


過去的一年多經歷了幾個App,總結下實踐下來覺得work得還不錯的方案。

基於配置檔案的頁面scheme

iOS router 設計

這是配置的頁面的scheme,scheme欄位用於標識一個頁面,在內部跳轉到遠端跳轉都需要用到,統一了內部和外部的跳轉。needLogin表示這個頁面在push的時候需要驗證登入,如果沒有登入的話就自動喚起登入頁面。allowRemote標識了這個頁面允許遠端跳轉。
對於外部跳轉,介面設計如下,url只需要傳入定義好協議的字串即可。

 [DRCRouter openURL:url];複製程式碼

對於內部跳轉,介面設計如下,params傳入dictionary,支援非序列化的物件。

 [DRCRouter pushWithSchema:@"orderDetail" params:@{@"orderId":orderId}];複製程式碼

其實外部跳轉只是內部跳轉的一個子集,對於通過openURL開啟native頁面的情況,最終也是經過解析呼叫pushWithSchema方法。

結合scheme的頁面回退

對於需要回退到棧底的某個頁面的時候,呼叫介面:

 [DRCRouter popToSchema:@"orderDetail"];複製程式碼

可以調轉到棧底的任意頁面,原理也是通過配置表去查詢scheme對應的vc。

前置攔截

為了安全性的需求,有些頁面只能允許內部跳轉,外部跳轉即使拼對了url也不允許跳轉。所以router內部會根據來源來判斷是否能跳轉到相應的頁面。並且,對於需要登入才能進入的頁面,會先彈出登入框使用者登入後再進入頁面。


未完待續。

相關文章