iOS启动速度优化之道

2,874 阅读7分钟

       App的启动速度,是用户体验中的一个很重要的组成部分,就好比人的第一印象,第一感觉决定了他们是否会继续交往。由此可见,App的启动速度是十分重要。当然,一般项目的初期阶段,对于启动速度的优化是非刚需的,因为一开始项目十分的小,而只有一般到了项目庞大了,启动的时候需要加载许多配置和第三方框架的初始化,这个时候对于启动速度就十分敏感了。

       本次我将会从App启动流程和优化两个方面去作分析以及表达自己的理解。

一、App的启动

       一般我们的启动分为冷启动和热启动,这里我简单介绍冷热启动的概念

  • 冷启动:App在启动的时候,即进入前台的时候,其应用的进程是不存在系统中的,系统需要创建一条进程供应用启动,而本次的优化也是针对这个过程
  • 热启动:App经过冷启动后已经显示在前台了,从前台退回后台,此时的进程还在系统中,还没被销毁,用户重新从后台进入前台的这个过程。在这个过程中我们作的优化也比较少。

       说完冷热启动,我们知道启动速度其实也是通过时间上来衡量的,用户感知启动时间慢,其实就是主线程慢,而主线程慢的原因有很多,比如

  • 启动时大文件的读写操作
  • 渲染时的大量计算
  • 其他

       通过上面我们知道启动慢,其实就是主线程有太多的耗时任务,进而影响了整个启动过程的耗时,而我们的目标很简单,那就是把启动过程中耗时的事情找出来,并根据需要酌情处理,提高启动的速度。

       而我们怎么找到启动中的耗时任务呢?在做这个的前提,我们要明白应用在启动的过程中做了什么事。

       一般启动可以分为三个阶段:

  • main()方法执行前
  • main()方法执行后
  • 首屏渲染完成

整个渲染流程的示意图如下所示:


       main()方法执行前,系统会作下面的几件事:

  • 加载可执行文件,即App的.o文件集合
  • 加载动态链接库,进行rebase指针调整和bind符号绑定
  • Ojbc运行时的初始化,包括Ojbc相关类的注册,分类注册等
  • 初始化,包括加载+load()方法,创建C++ 静态全局变量等

        这一块的知识就比较深了,我们就先不在这里详细展开,在这里我们可以作的优化如下:

  • 减少动态库在启动时候的加载数量,苹果本身就建议更少使用动态库,当动态库较多的时候可以选择合并动态库。
  • 减少加载启动后不会去使用的类或者方法,即我们要及时的去掉一些陈年又不会在使用的代码,做到定期清理和维护。
  • +load()方法里的内容尽可能放到首屏渲染完成后再执行,或者用+initialize()方法来替换,因为在一个load()方法里作方法替换,会带来4ms的延时,如果方法一多,其耗时还是比较可观的。
  • 控制C++ 全局变量

        main()方法执行后,一般我们指的是main()开始执行到AppDelegate的didFinishLaunchingWithOptions方法执行完成为止。

        通常,首屏的业务代码也在这里面了,这里我们会做的事有:

  • 首屏初始化所需要的配置文件的读写操作
  • 首屏数据的读写
  • 首屏渲染的大量计算
  • 第三库的初始化和加载

       在这的处理我们要根据需求来酌情处理了,梳理出哪些是主页渲染的必要功能,哪些可以在使用的时候按需加载,然后分别把初始化和加载分配到各个触发结点上,以此来均摊时间。

       首屏渲染完成后,一般是指didFinishLaunchingWithOptions方法作用域结束时到首屏渲染完成后,这个阶段用户可以看到主页了,但对于会卡住主线程的方法我们还是要按需处理掉,同时在这个阶段可以完成一些任务,如:

  • 非首屏其他业务模块的初始化
  • 其他业务的监听注册和配置等

       通过以上对App启动过程的分析,我们大概可以知道有哪些优化的点,接下来我们就从功能级别和方法级别提出优化的一些解决方案。

        功能级别的启动优化:

        我们都知道在项目的初期阶段,代码还是比较好控制,但到后面不断的换人,不断的增加人,启动的代码就会变得杂乱无章,各种堆积,无规范,难维护等问题,我现在所在的项目就有这样的问题。这种优化不仅仅靠一个人可以解决,而需要一个团队一起制定标准,做好文档的规范。

        在这里,我的优化思路是这样的,从main()方法执行后这一块入手,思路是:main()方法执行后到首屏渲染完成前,只作处理首屏相关业务,包括配置设置、文件读取、初始化必要的第三方库;而其他业务和与首屏无关的东西延迟到首屏渲染完成后。

       方法级别的启动优化:

       经过我们前面所做的将非首屏相关的初始化任务延迟到首屏渲染完成后去初始化,可以提升启动的速度,但这还不够,我们可以继续检查,在首屏渲染完成前,主线程中存在哪些耗时的方法,将没必要的耗时方法滞后或者异步执行,必要的耗时方法尽可能的做算法上的优化,这一块主要都是在加载、编辑、存储图片和文件资源了。

       除了上面我提到的图片文件等资源外,在前面一点提到的+load()也可能影响到启动速度等情况,那么我们就需要去借助一些工具来对启动过程中的函数作一个全面精确的检查和监控。

       我们可以使用Xcode提供的官方工具Time Profiler,使用它的优势是,使用成本低,Xcode本身已经完美支持;随着版本升级,可以享受它带来的优化。基本上满足使用。

       这里我就先不展开讲Time Profiler的使用方式了,其实还有一个火焰图,后面有需要再详细讲解,附上一篇使用文档:Instruments Tutorial with Swift: Getting Started

       总结:

       启动速度的优化和监控其实算是一个很重要的过程,不少产品都会在上面下功夫,比如某宝、 某信等大流量产品,这对用户的用户体验提升是最大的。

       我从两个方面对启动优化提出的自己的想法,从大的方面来讲,就是讲初始化的业务代码分类整理,合理地将与首屏无关的业务代码滞后或异步处理。从小的方面讲,我们可以借助Time Profiler等量化工具,对每个方法作细致分析和优化,提升启动速度。