原文地址: 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

iOS界面跳转的一些优化方案的更多相关文章

  1. ios界面跳转

    import Foundationimport UIKit class MyViewController: UIViewController{ // var window: UIWindow? ove ...

  2. 从350ms到80ms,揭秘阿里工程师 iOS 短视频优化方案

    内容作为 App 产品新的促活点,受到了越来越多的重视与投入,短视频则是增加用户粘性.增加用户停留时长的一把利器.短视频的内容与体验直接关系到用户是否愿意长时停留,盒马也提出全链路内容视频化的规划,以 ...

  3. ios中的界面跳转方式

    ios中,两种界面跳转方式 1.NavgationController本身可以作为普通ViewController的容器,它有装Controller的栈,所以可以push和pop它们,实现你所说的跳转 ...

  4. iOS 优化方案浅析

    本文转载至 http://mobile.51cto.com/iphone-413256.htm Windows独特的注册表机制以及复杂的进程.内存管理,给了很多PC“优化”类软件极大的机遇,比如奇虎3 ...

  5. iOS界面之间的跳转方式

    iOS界面之间的跳转方式基本有3种. .改变window的根视图 [self.window setRootViewController:VC]; .模态弹出 [self presentViewCont ...

  6. iOS应用 跳转到系统的设置界面

    现在很多APP都需要获取用户权限,例如,允许调用位置信息,读取短信,拨打电话,开启WIFI,掉头摄像头等,用户不允许APP获取这些权限的时候.最好的用户体验是,直接跳转到系统设置界面,让用户自己设置. ...

  7. ios的两种界面跳转方式

    1.在界面的跳转有两种方法,一种方法是先删除原来的界面,然后在插入新的界面,使用这种方式无法实现界面跳转时的动画效果. if(self.rootViewController.view.supervie ...

  8. iOS Scheme 跳转主流实现方案

    iOS Scheme跳转主流实现方案主要是路由跳转,目前iOS常用路由框架是JLRouter.HHRouter.MGJRouter. 但是这些路由库都各有不足,首先是JLRouter,用不到的功能繁多 ...

  9. [转] 钉钉的H5性能优化方案

    对于一个H5的产品,功能无疑很重要,但是性能同样是用户体验中不可或缺的一环.原本H5的渲染性能就不及native的app,如果不把性能优化做起来,将极大地影响用户使用产品的积极性. 用户感受 当用户能 ...

随机推荐

  1. [2016湖南长沙培训Day4][前鬼后鬼的守护 chen] (动态开点线段树+中位数 or 动规 or 贪心+堆优化)

    题目大意 给定一个长度为n的正整数序列,令修改一个数的代价为修改前后两个数的绝对值之差,求用最小代价将序列转换为不减序列. 其中,n满足小于500000,序列中的正整数小于10^9 题解(引自mzx神 ...

  2. SQL基础--ROWNUM伪列

    ROWNUM伪列 的概念: rownum表示的是一个数据行编号的伪列,它的内容是在用户查询数据时,为用户动态分配的一个数组(行号). 注意它是随机生成的,并不是和某一行数据绑定在一起的. 观察rown ...

  3. C++与C# UDP通信实例(同一台PC)

    对于同一个PC机而言,服务器端和客户端在一个PC机上面,端口必须要不一样,不然会冲突. 你总不能自己又当爹又当妈吧. 所以在进行程序设计的时候,需要考虑这一点: 在此接口设计中,C++当作UDP的服务 ...

  4. php $CI =& get_instance();

    初学php 看有人这样写,$CI =& get_instance(); 要你自定义的类库中访问CodeIgniter的原始资源,你必须使用 get_instance() 函数.这个函数返回一个 ...

  5. python 环境搭建

    python下载地址: 进入https://www.python.org/download/releases/3.3.4/,下载Windows X86-64 MSI Installer (3.3.4) ...

  6. knockoutJS学习笔记09:使用mapping插件

    一.问题引出 通常,我们先定义好html结构或者模板,通过ajax向后台发起请求,后台返回json数据,然后再将json数据渲染到页面上.以博客园个人博客里的个人信息为例子,如图: 1.定义html. ...

  7. 【跟着子迟品 underscore】JavaScript 数组展开以及重要的内部方法 flatten

    Why underscore (觉得这一段眼熟的童鞋可以直接跳到正文了...) 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中. ...

  8. JS组件系列——Bootstrap右键菜单解决方案:ContextMenu

    前言:有段时间没发表随笔了,过个年人都变得懒了.新年刚来上班,今天正好得空,将去年遗留的两个小组件总结记录下.有朋友跟我说:你的bootstrap组件要能够形成一个可以满足一般项目需求的系列组件,才有 ...

  9. 反射在ADO.NET中的运用(你还在每个项目中循环遍历DataTable吗)

    图片有点大哈,但大更能说明问题.您是不是每个项目都在重复的做图片中的事情-----循环把数据库中返回的表转化为实体对象.是不是每次都在抱怨这样的重复工作.字段越多抱怨越多!不用抱怨了.当你看到这篇文章 ...

  10. 给RecyclerView最纯粹的下拉刷新和上拉加载更多

    转自 http://blog.csdn.net/jerrywu145/article/details/52225898 http://www.jianshu.com/p/3bf125b4917d