iOS启动优化之二进制重排

语言: CN / TW / HK

随着iOS项目越来越大,启动时间也会变得越来越慢,我们如何来对app的启动时间来进行优化,给用户一个更好的体验呢?

应用启动介绍

应用启动分为冷启动和热启动

  • 冷启动是指内存中不存在与应用相关的数据,需要从硬盘时将应用的数据载入到内存中;这个过程是由系统决定的,平时我们把应用杀死,然后马上启动,此时内存中的应用相关的数据不会马上被清除,所以这时候的启动不是冷启动;
  • 热启动是指内存中还存留着应用相关的数据,应用运行所需要的数据不需要全部从硬盘中载入内存。

监控启动时间

我们可以通过设置环境变量来起到监控应用启动时间的目的,有关环境变量的设置,请参考Xcode环境变量

我们设置环境变量DYLD_PRINT_STATISTICS,运行工程,打印应用启动时的时间消耗情况:

打印出应用启动时间如下:

我们以main函数为分隔点,将应该启动时间优化分为main函数之前和main函数之后,这里所打印的pre-main time就是main函数之前的耗时。

  • dylib loading time:动态库的载入耗时,苹果建议项目中自定义的动态库最好不要超过6个动态库,如果超过了6个要考虑动态库合并;
  • rebase/binding time:偏移修正(rebase)和符号绑定(binding)的耗时;偏移修正是一个安全机制--ASLR,原理是生成一个随机值,加到应用内存地址的前面,来起到方法、函数的地址随机的目的,解决安全问题.所有的方法、函数和数据的内存地址都会在原有的真实地址上加上这个随机值,并且每次启动的时候这个随机值是不同的,这样别人就没法拿到方法、函数或数据的真实地址了;符号绑定是指将方法名与方法的实现,即sel和IMP的绑定。
  • ObjC setup time:Object-C类的注册的耗时;不权威统计,每增加20000个类,这个时间增加800ms;减少类的数量可以减少启动耗时;
  • initializer time:load方法和构造方法的耗时.所以项目中要尽量减少重写load 方法,将load方法的操作放在initialization中.

以上就是main函数之前的启动耗时说明及相关优化方案;

对于main函数之后的启动耗时,我们可以通过代码的方式进行监控,比如,在main函数中记录一个时间,到第一个界面显示的viewDidLoad方法中记录一个时间,两个时间的差值就是main函数之后的启动耗时.由于main函数之后的耗时需要根据不同的项目来区分,不能一概而论,这里提供几个优化的建议

  • 减少数据的加载,最好使用懒加载,如一个第三文库的加载;
  • 将项目中不再使用的类和方法去掉;
  • 使用多线程技术加载数据,充分利用cpu的资源;
  • 启动时刻的界面,不要使用storyboard或xib,尽量使用纯代码,因为storyboard或xib有一步代码转换的操作,也是会耗时的。

二进制重排

技术背景

应用程序在运行时,使用的是虚拟内存与物理内存相结合的方法加载数据的;虚拟内存是分页管理的,当使用到某一页虚拟内存的数据时,需要将对应的真实数据加载到物理内存中,将虚拟内存与物理内存之前形成一个映射关系,这个操作称为缺页异常(page fault),这个操作是需耗时的。

查看iOS应用的载入数据的顺序,我们可以通过设置Write Link Map File为yes,来查看应用数据加载的顺序

clean一下工程,然后command+B编绎一下工程,来到Products的xx.app目录下

再来到如下目录下

找到如下文件

打开此文件Demo-LinkMap-normal-x86_64.txt

这里的顺序就是应用数据加载的顺序,这里的顺序是由下以两个因素决定的

按文件顺序从上到下

按文件中的方法的顺序从上到下

验证一下此文件的顺序就是实际的载入顺序,可以通过修改文件或方法的顺序,然后重新编绎后查看link map文件的方法顺序是否改变。

二进制重排方案

由于我们在应用在启动时可能只需要某些文件中的某些方法,而按照实际的方法载入顺序,在应用启动时会因为page fault的存在,就需要载入大量的用不到的数据,造成了大量的时间消耗,为此我们只需要在应用启动时只载入启动时需要的方法就可以了,其实的数据可以后续载入;为此我们可以通过创建一个.order文件来修改方法或函数的加载顺序.

打开hank.order文件,查看以下几个方法的顺序

将此文件配置到对应的xcode工程中

clean后重新编绎,查看Demo-LinkMap-normal-x86_64文件

这样我们就成功的修改了方法或函数的加载顺序,所以我们只需要知道应用启动时执行了哪些方法,将他们排序好,放在.order文件中,就可以在应用启动时,只加载了自己需要的数据,减少了page fault的次数,从而减少了启动耗时。

那么如何知道应用启动时执行了哪些方法呢?请看Hook一切的终极武器--Clang插桩

分享到: