iOS介面跳轉的一些優化方案
App應用程式開發, 介面跳轉是基礎中的基礎, 幾乎沒有一個App是用不到介面跳轉的, 那麼怎麼樣去書寫介面跳轉程式碼才是比較合理的呢?
大家可能在想跳轉無非就2種方式, 能有什麼內容? 其實並不是這樣子的, 對於研發老手來說, 大型應用幾乎都是利用URLScheme進行全方位的解決方案; 對於研發新手來說, 他們可能並沒有遇到多路口介面跳轉的瓶頸, 只會使用一些常用跳轉, 並不會意識到介面跳轉潛在的一些問題, 甚至無法嚴格區分Present和Push的操作區別~
本文將針對介面跳轉提出一些優化解決方案~
常用跳轉方式
iOS常用的跳轉方式只有兩種Present和Push。Push和Present最直觀的區別是預設的轉場效果, Present的預設轉場效果是自下而上的, Push的轉場效果是自右到左的。Push往往需要搭配UINavigationController來使用。
Push跳轉使用示意:
UIViewController *nextViewController = [[UIViewController alloc] init];
nextViewController.title = @"第二個介面";
[self.navigationController pushViewController:nextViewController animated:YES];
Present跳轉使用示意:
UIViewController *nextViewController = [[UIViewController alloc] init];
nextViewController.title = @"第二個介面";
[self presentViewController:nextViewContrller animated:YES completed:nil];
在大部分情況下, iOS研發者都在濫用Push的跳轉方式, 往往一個App僅僅包含一個UINavigationController。產生濫用的原因是因為Present的跳轉太過難於定製轉場效果, 僅僅只能使用系統提供的4種開啟方式。
在滿足產品要求的前提下, 其實可以基於模組內跳轉和模組外跳轉的思量去考慮採用Present的方式還是Push的方式去跳轉介面。
PS: 還有一種介面切換方式是利用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解決方案:
- Routable Android和iOS均支援的一款權威的應用內URL跳轉路由, 幾乎可以滿足所有需求
- 程式碼切入性比較低, 沒有冗餘的繼承封裝。
- 可以指定NavigationController, 方便定製ChildController的跳轉
- 可以多個Router組合使用, 靈活性高
- urlmananger 國內技術問題網站segmentfault.com開發者抽離的一個跳轉器
- 需要嵌入繼承和繫結使用NavigationController, 架構設計層級嵌入性很高, 沒有Routable合理
- 無法多個組合使用, 靈活性沒有Routable高
- 封裝層次高, 快速使用可以採用
個人比較傾向使用Routable, 因為並沒有在架構上對程式碼進行嵌入, 比較符合開發者口味~
以Routable作為示例, 本文可以通過如下程式碼在App啟動的時候就提前註冊(PS: Push點選開啟執行Optional引數之前註冊)
[[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進行傳遞呢?
結合本人喜歡使用的庫YYModel, 我想到了一種暴力且耗時的解決方案, 但是至少不產生耦合哈~
暴力解決方案步驟:
- 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來進行統一管理。
根據個人在介面跳轉開發中遇到的困難, 提出了幾個相應的瓶頸和對應的解決方案。針對傳遞物件值提出了一種暴力但是耦合度比較低的解決方案。