原文地址: http://blog.startry.com/2016/02/14/Think-Of-UIViewController-Switch/
iOS界面跳轉的一些優化方案
App應用程序開發, 界面跳轉是基礎中的基礎, 幾乎沒有一個App是用不到界面跳轉的, 那么怎么樣去書寫界面跳轉代碼才是比較合理的呢?
大家可能在想跳轉無非就2種方式, 能有什么內容? 其實并不是這樣子的, 對于研發老手來說, 大型應用幾乎都是利用URLScheme進行全方位的解決方案; 對于研發新手來說, 他們可能并沒有遇到多路口界面跳轉的瓶頸, 只會使用一些常用跳轉, 并不會意識到界面跳轉潛在的一些問題, 甚至無法嚴格區分Present和Push的操作區別~
本文將針對界面跳轉提出一些優化解決方案~
常用跳轉方式
iOS常用的跳轉方式只有兩種Present和Push。Push和Present最直觀的區別是默認的轉場效果, Present的默認轉場效果是自下而上的, Push的轉場效果是自右到左的。Push往往需要搭配UINavigationController來使用。
Push跳轉使用示意:
1
UIViewController *nextViewController = [[UIViewController alloc] init];nextViewController.title = @"第二個界面";[self.navigationController pushViewController:nextViewController animated:YES];
Present跳轉使用示意:
1
UIViewController *nextViewController = [[UIViewController alloc] init];nextViewController.title = @"第二個界面";[self presentViewController:nextViewContrller animated:YES completed:nil];
在大部分情況下, iOS研發者都在濫用Push的跳轉方式, 往往一個App僅僅包含一個UINavigationController。產生濫用的原因是因為Present的跳轉太過難于定制轉場效果, 僅僅只能使用系統提供的4種打開方式。
在滿足產品要求的前提下, 其實可以基于模塊內跳轉和模塊外跳轉的思量去考慮采用Present的方式還是Push的方式去跳轉界面。
PS: 還有一種界面切換方式是利用ChildViewController, 進行獨立界面的控制。需要高度定制的界面可以采用這種方式, 例如基于地圖或者相機的應用。基于ChildViewController的方式內容篇幅太長, 在本文就暫時不介紹了, 以后再補充~
常用跳轉方式瓶頸
常用的跳轉方式其實已經幾乎可以滿足我們所有的跳轉, 但是欠缺一層業務層次的高層級封裝。
舉一個實際使用場景, 某App支持4種頁面打開方式:
Push推送
App外部網頁打開
App內部網頁打開
應用內點擊打開
這四種方式均跳轉到DetailViewController界面。普通的跳轉依然可以滿足該場景, 最簡單的解決方案是在四個不同的地方都寫一個獨立的界面打開邏輯。
作為一名業務模塊人才, 如此不復用代碼合理么? 作為一名App架構師, 如此冗余四份入口代碼合適么?
如果您覺得不合適, 那自然需要去抽離代碼到統一的地方去書寫一套統一的管理邏輯; 如果您覺得合適, 那么您會設想什么方案去根據外部網頁以及Push內容去轉化代碼至普通跳轉代碼呢?
URLScheme解決方案
我們先根據前面提到的四種場景進行場景分析:
外部網頁場景:
只能使用iOS自帶的URLScheme的方式去打開App, 然后通過AppDelegate中事件去獲取對應的URL進行匹配
Push推送場景:
在extra字段中定義個鏈接字段, 鏈接字段是個字符串或者數字代號, 用于在AppDelegate中事件去獲取對應的代號進行匹配; 既然代碼是自定義的, 那自然可以定義成一個URL了
內部網頁場景
熟悉iOS WebView開發的童鞋們都知道, UIWebView的JS交互本質上是通過截獲URL請求去實現的, 那么既然是傳遞URL地址, 就可以和外部網頁使用相關的方式, 只不過在不同的位置進行對應的URL匹配
應用內點擊打開
可以采用普通打開方式, 也可以通過一個抽離的URLScheme匹配器去匹配打開
根據上述四個場景, 我們可以發現, 解決上述四個應用場景, 我們需要的是引入一個抽離的URLScheme匹配器去匹配打開輪轉界面~
利用URLScheme的方式進行一層封裝, 幾乎可以完美解決多入口打開App的邏輯復用問題。
PS: 一般情況下, URLScheme的抽離器不需要自己封裝, 可以使用開源現成的, 很少場景需要高度定制。
開源URLScheme解決方案:
RoutableAndroid和iOS均支持的一款權威的應用內URL跳轉路由, 幾乎可以滿足所有需求
代碼切入性比較低, 沒有冗余的繼承封裝。
可以指定NavigationController, 方便定制ChildController的跳轉
可以多個Router組合使用, 靈活性高
urlmananger國內技術問題網站segmentfault.com開發者抽離的一個跳轉器
需要嵌入繼承和綁定使用NavigationController, 架構設計層級嵌入性很高, 沒有Routable合理
無法多個組合使用, 靈活性沒有Routable高
封裝層次高, 快速使用可以采用
個人比較傾向使用Routable, 因為并沒有在架構上對代碼進行嵌入, 比較符合開發者口味~
以Routable作為示例, 本文可以通過如下代碼在App啟動的時候就提前注冊(PS: Push點擊打開執行Optional參數之前注冊)
1
[[Routable sharedRouter] map:@"detail/:id" toController:[DetailController class]];
假設您的App URLScheme前綴為demo123, 您只需要在上述四個場景分別傳遞demo123://detail/88過來即可。88只是示例的一個隨意亂寫的id編號, 作為Restful分格的參數進行處理。
URLScheme些許問題
URLScheme進行界面跳轉的解決方案也不是完美的, 個人開發時候遇到最大的問題就是傳值問題
怎么傳遞對象值
URLScheme原則上不支持傳遞復雜的對象, 通過URLScheme方式打開的界面理論上每個界面都相對保持邏輯獨立(邏輯獨立的代價往往是犧牲細微的用戶體驗), 邏輯獨立的界面可以有更好的架構設計
通過外部URL或者Push的方式是無法傳遞對象的, 可以不用考慮傳遞對象的場景
應用內界面跳轉可以根據實際場景去區分使用URLScheme的方式還是普通的方式進行界面跳轉控制
怎么傳遞URL值
URLScheme打開的界面有時候也需要傳遞URL值用于對應的界面, 最常見的是打開圖片管理器以及打開WebView的界面, 這種場景可以采用約定加密的方式進行處理, 對傳遞的URL進行URIEncode和取值時候的URIDecode。
Push長度限制
坑爹的APNs規定了Push內容的總長度不能大于255字節, 那么URLScheme的參數傳遞就收到限制。
最常用的解決方案是壓縮字段名字和內容, 傳遞的字段勁量用一個單詞表示, 值字段可以隱藏掉URLScheme的前綴, 只保留后綴以及參數。
還有一種通過的Push長度解決方案是, push只是觸發器, 觸發App請求去獲取真正的內容來繞過長度限制。
傳值對象
此處針對應用內跳轉使用url scheme還是普通方式進行一些議論, 個人覺得一套應用里如果有兩種方式跳轉, 雖然靈活性高, 但是比較難以控制, 因此最好都采用一套方式跳轉, 那自然是使用url scheme。那復雜傳值的問題依舊無法得到解決。
有些開發者為了省時間, 直接通過類似Notification的方式或者用單例對象去維護進行值傳遞, 也不失為一個方法。
但是我在思考, 有沒有一種相對完美的解決方案, 能夠將傳值問題徹底用url scheme進行傳遞呢?
結合本人喜歡使用的庫JSONModel, 我想到了一種暴力且耗時的解決方案, 但是至少不產生耦合哈~
暴力解決方案步驟:
JSON化對象
將對象JSON字符串Base64加密
將Base64加密后的字符串作為url參數傳遞
接受者處理參數的時候反Base64解密
將解密后的JSON對象用模型實例化
針對暴力解決方案, 本人設想了四個自問自答:
為什么不直接Base64對象而需要將其JSON字符串話呢?
答: 為了接受者解密后的直觀性。在大型App開發過程中, 解密者解析后不一定知道用哪個模型去實例化JSON字符串, 通過這種方式, 解密者可以不關心接受數據后的模型實例而自由發揮。
利用這種方式暴力解決后, url會不會很長?
答: 這種方式傳遞的url會超級長, 但是在應用內進行頁面處理的場景, 不需要可以的去考慮url的長度。但是url的長度可能會影響解析的性能。
為什么不直接通過廣播或者單例維護的方式傳值?
答: 為了解耦。 大型App維護的時候, 如果一個內存對象是公用的, 是十分難以維護的, 應該盡量減少傳遞對象之間的耦合。
為什么需要base64加密而不直接采用uriencode的方式?
答: 為了解決模型嵌套的問題。因為一個模型里可能會嵌套另外一個模型, 當然通過JSON字符串本身可以實現模型嵌套的解決, 那可以有更加節省性能的解決方案。
本人水平有限, 此處的暴力的方式是目前本人覺得相對耦合度低并且比較好的一種解決方案。對于目前的iOS手機設備來說, 這點JSON以及解密的性能并不能影響整個App的運行, 因此采用這種方式進行暴力解決。
總結
頁面輪轉在小型的App并不需要針對單獨進行優化設計, 但是對于上20w行代碼的大型App來說, 往往都是需要針對優化的。
本文引用了市面上最通用的URLScheme的解決方案來進行頁面跳轉的設計優化, 并建議采用開源庫Routable來進行統一管理。
根據個人在界面跳轉開發中遇到的困難, 提出了幾個相應的瓶頸和對應的解決方案。針對傳遞對象值提出了一種暴力但是耦合度比較低的解決方案。
PS: 本人水平有限, 有錯誤的地方還望大家及時指出~ 謝謝!