原文地址: 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. 数字图像处理中的4邻接,8邻接与m邻接

    像素之间的邻接性: 4邻接.如果q在集合N4(p)中,则具有V中数值的两个像素p和q是4邻接的. 8邻接.如果q在集合N8(p)中,则具有V中数值的两个像素p和q是8邻接的. m邻接(混合邻接).如果 ...

  2. 代理延迟加载中proxy和弄no-proxy区别

    Child   <-   many-to-one   ->Parent         class   Child   {         private   Parent   paren ...

  3. Map接口,Map.Entry,hashMap类,TreeMap类,WeakHashMap。

    Collection接口之前接触过,每次保存的对象是一个对象,但是在map中保存的是一对对象,是以key->value形式保存的. 定义: public interface Map<K,V ...

  4. haproxy 新手上路

    apache.nginx之类的反向代理(转发)功能,通常只能用于http协议,其它协议就不好使了(注:nginx据说商业版的,支持tcp协议了). haproxy可以弥补这方面的不足,haproxy支 ...

  5. JS组件系列——表格组件神器:bootstrap table(三:终结篇,最后的干货福利)

    前言:前面介绍了两篇关于bootstrap table的基础用法,这章我们继续来看看它比较常用的一些功能,来个终结篇吧,毛爷爷告诉我们做事要有始有终~~bootstrap table这东西要想所有功能 ...

  6. 新的一年快开始了,学点新东西吧,从React开始(一)

    ReactJS是Facebook出的前端View框架,好东西啊. 看看它的说明: 仅仅是UI 许多人使用React作为MVC架构的V层. 尽管React并没有假设过你的其余技术栈, 但它仍可以作为一个 ...

  7. 移动UI设计

    移动应用UI设计模式 第二版 导航:跳板式,菜单式,选项卡式(微信)等 表单:登录表单,多步骤表单(递进式),计算表单,搜索表单,长表单等 表格:无表头表格,概览+数据型表格等 搜索:隐式搜索(滴滴打 ...

  8. 随堂软工团队小测——git协同

    No Bug 031402401鲍亮 031402402曹鑫杰 031402403常松 031402412林淋 031402418汪培侨 031402426许秋鑫 功能模块划分 方法 功能 main( ...

  9. HTML5 屏蔽触屏滚动

    开发移动的html应用时常常需要将网页触控事件屏蔽掉.代码如下: //屏蔽全局触控事件 document.ontouchmove = function(e){ e.preventDefault();} ...

  10. javaScript获取url中的参数

    var urlTools = { //获取RUL参数值 getUrlParam: function(name) { /*?videoId=identification */ var params = ...