iOS界面跳转的一些优化方案

原文地址: http://blog.startry.com/2016/02/14/Think-Of-UIViewController-Switch/

iOS界面跳转的一些优化方案

App应用程序开发, 界面跳转是基础中的基础, 几乎没有一个App是用不到界面跳转的, 那么怎么样去书写界面跳转代码才是比较合理的呢?

大家可能在想跳转无非就2种方式, 能有什么内容? 其实并不是这样子的, 对于研发老手来说, 大型应用几乎都是利用URLScheme进行全方位的解决方案; 对于研发新手来说, 他们可能并没有遇到多路口界面跳转的瓶颈, 只会使用一些常用跳转, 并不会意识到界面跳转潜在的一些问题, 甚至无法严格区分Present和Push的操作区别~

本文将针对界面跳转提出一些优化解决方案~

常用跳转方式

iOS常用的跳转方式只有两种PresentPush。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种页面打开方式:

  1. Push推送
  2. App外部网页打开
  3. App内部网页打开
  4. 应用内点击打开

这四种方式均跳转到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解决方案:

  1. Routable Android和iOS均支持的一款权威的应用内URL跳转路由, 几乎可以满足所有需求
    • 代码切入性比较低, 没有冗余的继承封装。
    • 可以指定NavigationController, 方便定制ChildController的跳转
    • 可以多个Router组合使用, 灵活性高
  2. 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进行界面跳转的解决方案也不是完美的, 个人开发时候遇到最大的问题就是传值问题

  1. 怎么传递对象值
    • URLScheme原则上不支持传递复杂的对象, 通过URLScheme方式打开的界面理论上每个界面都相对保持逻辑独立(逻辑独立的代价往往是牺牲细微的用户体验), 逻辑独立的界面可以有更好的架构设计
    • 通过外部URL或者Push的方式是无法传递对象的, 可以不用考虑传递对象的场景
    • 应用内界面跳转可以根据实际场景去区分使用URLScheme的方式还是普通的方式进行界面跳转控制
  2. 怎么传递URL值
    • URLScheme打开的界面有时候也需要传递URL值用于对应的界面, 最常见的是打开图片管理器以及打开WebView的界面, 这种场景可以采用约定加密的方式进行处理, 对传递的URL进行URIEncode和取值时候的URIDecode。
  3. Push长度限制
    • 坑爹的APNs规定了Push内容的总长度不能大于255字节, 那么URLScheme的参数传递就收到限制。
    • 最常用的解决方案是压缩字段名字和内容, 传递的字段劲量用一个单词表示, 值字段可以隐藏掉URLScheme的前缀, 只保留后缀以及参数。
    • 还有一种通过的Push长度解决方案是, push只是触发器, 触发App请求去获取真正的内容来绕过长度限制。

传值对象

此处针对应用内跳转使用url scheme还是普通方式进行一些议论, 个人觉得一套应用里如果有两种方式跳转, 虽然灵活性高, 但是比较难以控制, 因此最好都采用一套方式跳转, 那自然是使用url scheme。那复杂传值的问题依旧无法得到解决。

有些开发者为了省时间, 直接通过类似Notification的方式或者用单例对象去维护进行值传递, 也不失为一个方法。

但是我在思考, 有没有一种相对完美的解决方案, 能够将传值问题彻底用url scheme进行传递呢?

结合本人喜欢使用的库JSONModel, 我想到了一种暴力且耗时的解决方案, 但是至少不产生耦合哈~

暴力解决方案步骤:

  1. JSON化对象
  2. 将对象JSON字符串Base64加密
  3. 将Base64加密后的字符串作为url参数传递
  4. 接受者处理参数的时候反Base64解密
  5. 将解密后的JSON对象用模型实例化

针对暴力解决方案, 本人设想了四个自问自答:

为什么不直接Base64对象而需要将其JSON字符串话呢?

答: 为了接受者解密后的直观性。在大型App开发过程中, 解密者解析后不一定知道用哪个模型去实例化JSON字符串, 通过这种方式, 解密者可以不关心接受数据后的模型实例而自由发挥。

利用这种方式暴力解决后, url会不会很长?

答: 这种方式传递的url会超级长, 但是在应用内进行页面处理的场景, 不需要可以的去考虑url的长度。但是url的长度可能会影响解析的性能。

为什么不直接通过广播或者单例维护的方式传值?

答: 为了解耦。 大型App维护的时候, 如果一个内存对象是公用的, 是十分难以维护的, 应该尽量减少传递对象之间的耦合。

为什么需要base64加密而不直接采用uriencode的方式?

答: 为了解决模型嵌套的问题。因为一个模型里可能会嵌套另外一个模型, 当然通过JSON字符串本身可以实现模型嵌套的解决, 那可以有更加节省性能的解决方案。

本人水平有限, 此处的暴力的方式是目前本人觉得相对耦合度低并且比较好的一种解决方案。对于目前的iOS手机设备来说, 这点JSON以及解密的性能并不能影响整个App的运行, 因此采用这种方式进行暴力解决。

总结

页面轮转在小型的App并不需要针对单独进行优化设计, 但是对于上20w行代码的大型App来说, 往往都是需要针对优化的。

本文引用了市面上最通用的URLScheme的解决方案来进行页面跳转的设计优化, 并建议采用开源库Routable来进行统一管理。

根据个人在界面跳转开发中遇到的困难, 提出了几个相应的瓶颈和对应的解决方案。针对传递对象值提出了一种暴力但是耦合度比较低的解决方案。

PS: 本人水平有限, 有错误的地方还望大家及时指出~ 谢谢!

参考文献

  1. Apple官方文档 - UIViewController
  2. Apple官方文档 - UINavigationController
  3. Apple官方文档 - URLScheme
  4. Apple官方文档 - APNs
原文地址:https://www.cnblogs.com/apem/p/5317859.html