iOS客户端启动优化

App启动分成两部分

  • pre-main 阶段的定义为 APP 开始启动到系统调用 main 函数这一段时间
  • main 阶段则代表从main函数入口到主 UI 框架的 viewDidAppear

Pre-main

dyld:dynamic loader,它的作用是加载一个进程所需要的image,dyld是开源的

Pre-main的流程.png

Load Dylibs

dyld加载App的可执行文件,App内嵌的库,系统动态库。每个库,就是Mach-o文件,其头部都包含依赖库信息,dyld会递归加载这些库。

  • 系统库会有缓存,dyld共享库缓存,加载很快
  • 内嵌的库加载较慢

加载过程

  1. 分析所依赖的动态库
  2. 找到动态库的mach-o文件
  3. 打开文件
  4. 验证文件
  5. 在系统核心注册文件签名
  6. 对动态库的每一个segment调用mmap()

优化

  • 减少非系统库的依赖
  • 使用静态库而不是动态库
  • 合并多个非系统动态库为一个动态库

Rebase && Binding

ASLR的全称是Address space layout randomization,翻译过来就是“地址空间布局随机化”。App被启动的时候,程序会被影射到逻辑的地址空间,这个逻辑的地址空间有一个起始地址,而ASLR技术使得这个起始地址是随机的。如果是固定的,那么黑客很容易就可以由起始地址+偏移量找到函数的地址。

  • Rebase 修正内部(指向当前mach-o文件)的指针指向。是因为刚刚提到的ASLR使得地址随机化,导致起始地址不固定,另外由于Code Sign,导致不能直接修改Image。Rebase的时候只需要增加对应的偏移量即可。待Rebase的数据都存放在__LINKEDIT中
  • Bind 修正外部指针指向。外部的符号引用则是由Bind解决。在解决Bind的时候,是根据字符串匹配的方式查找符号表,所以这个过程相对于Rebase来说是略慢的


    iOS客户端启动优化_第1张图片
    Rebase && Binding.png

优化

  • 减少Objc类数量, 减少selector数量,把未使用的类和函数都可以删掉。跟单职责有点冲突?
  • 减少C++虚函数数量
  • 转而使用swift stuct(其实本质上就是为了减少符号的数量,使用swift语言来开发?)

OBJC Runtime Setup

  • 读取二进制文件的 DATA 段内容,找到与 objc 相关的信息
  • 注册 Objc 类,ObjC Runtime 需要维护一张映射类名与类的全局表。当加载一个 dylib 时,其定义的所有的类都需要被注册到这个全局表中;
  • 读取 protocol 以及 category 的信息,把category的定义插入方法列表 (category registration),
  • 确保 selector 的唯一性

Initializers

以上三步属于静态调整,都是在修改__DATA segment中的内容,而这里则开始动态调整,开始在堆和栈中写入内容。 工作主要有:

  • Objc的+load()函数
  • C++的构造函数属性函数 形如attribute((constructor)) void DoSomeInitializationWork()
  • 非基本类型的C++静态全局变量的创建。(通常是类或结构体)(non-trivial initializer) 比如一个全局静态结构体的构建,如果在构造函数中有繁重的工作,那么会拖慢启动速度

优化

  • 使用 +initialize 来替代 +load
  • 不要使用 atribute((constructor)) 将方法显式标记为初始化器,而是让初始化方法调用时才执行。
    • 比如使用 dispatch_once(),pthread_once()std::once()。也就是在第一次使用时才初始化,推迟了一部分工作耗时。
    • 也尽量不要用到C++的静态对象。

Pre-main 总结

  1. pre-main阶段耗时的影响因素:

    • 动态库加载越多,启动越慢。
    • ObjC类越多,函数越多,启动越慢。
    • 可执行文件越大启动越慢。
    • C的constructor函数越多,启动越慢。
    • C++静态对象越多,启动越慢。
    • ObjC的+load越多,启动越慢。
  2. 整体上pre-main阶段的优化有:

    • 减少依赖不必要的库,不管是动态库还是静态库;如果可以的话,把动态库改造成静态库;如果必须依赖动态库,则把多个非系统的动态库合并成一个动态库;
    • 检查下 framework应当设为optional和required,如果该framework在当前App支持的所有iOS系统版本都存在,那么就设为required,否则就设为optional,因为optional会有些额外的检查;
    • 合并或者删减一些OC类和函数;关于清理项目中没用到的类,使用工具AppCode代码检查功能,查到当前项目中没有用到的类(也可以用根据linkmap文件来分析,但是准确度不算很高);有一个叫做FUI的开源项目能很好的分析出不再使用的类,准确率非常高,唯一的问题是它处理不了动态库和静态库里提供的类,也处理不了C++的类模板。
    • 删减一些无用的静态变量,
    • 删减没有被调用到或者已经废弃的方法
      • http://stackoverflow.com/questions/35233564/how-to-find-unused-code-in-xcode-7
      • https://developer.apple.com/library/archive/documentation/ToolsLanguages/Conceptual/Xcode_Overview/CheckingCodeCoverage.html
    • 将不必须在+load方法中做的事情延迟到+initialize中,尽量不要用C++虚函数(创建虚函数表有开销)
    • 类和方法名不要太长:iOS每个类和方法名都在__cstring段里都存了相应的字符串值,所以类和方法名的长短也是对可执行文件大小是有影响的;因还是object-c的动态特性,因为需要通过类/方法名反射找到这个类/方法进行调用,object-c对象模型会把类/方法名字符串都保存下来;
    • 用dispatch_once()代替所有的 attribute((constructor)) 函数、C++静态对象初始化、ObjC的+load函数;
    • 在设计师可接受的范围内压缩图片的大小,会有意外收获。压缩图片为什么能加快启动速度呢?因为启动的时候大大小小的图片加载个十来二十个是很正常的,图片小了,IO操作量就小了,启动当然就会快了,比较靠谱的压缩算法是TinyPNG。

Main

总体原则无非就是减少启动的时候的步骤,以及每一步骤的时间消耗。
main阶段的优化大致有如下几个点:

  • 减少启动初始化的流程,能懒加载的就懒加载,能放后台初始化的就放后台,能够延时初始化的就延时,不要卡主线程的启动时间,已经下线的业务直接删掉;
  • 优化代码逻辑,去除一些非必要的逻辑和代码,减少每个流程所消耗的时间;
  • 启动阶段使用多线程来进行初始化,把CPU的性能尽量发挥出来;
  • 使用纯代码而不是xib或者storyboard来进行UI框架的搭建,尤其是主UI框架比如TabBarController这种,尽量避免使用xib和storyboard,因为xib和storyboard也还是要解析成代码来渲染页面,多了一些步骤;
  • 主UI框架tabBarController的viewDidLoad函数里,去掉一些不必要的函数调用

其中遇到几个坑:

  • 并不是什么任务都适合放子线程,有些任务在主线程大概10ms,放到子线程需要几百ms,因为某些任务内部可能会用到UIKit的api,又或者某些操作是需要提交到主线程去执行的,关键是要搞清楚这个任务里边究竟做了啥,有些SDK并不见得适合放到子线程去初始化,需要具体情况具体去测试和分析。

实际优化效果

由于只是去掉了几个静态库,而且本来pre-main阶段的耗时就不长,基本在200ms-500ms左右,所以pre-main阶段优化前后效果并不明显,有时候还没有前后测试的误差大。。。
main的阶段的优化效果还是很明显的:

  • iPhone5C iOS10.3.3系统优化前main阶段的时间消耗为4秒左右,优化后基本在1.8秒内;
  • iPhone7 iOS10.3.3系统优化前main阶段的时间消耗为1.1秒左右,优化后基本在600ms内;
  • iPhoneX iOS11.3.1系统优化前main阶段的时间消耗基本在1.5秒以上,优化后在1秒内;

可以看到,同样arm64架构的机器,main阶段是iPhone7比iPhoneX更快,说明就操作系统来说,iOS11.3要比iOS10.3慢不少;

参考文章

  1. 今日头条 http://www.cocoachina.com/ios/20170208/18651.html
  2. http://yulingtianxia.com/blog/2016/10/30/Optimizing-App-Startup-Time/
  3. 综合 https://mp.weixin.qq.com/s/zeWfmAi0YnoQowcPpFhHUA
  4. https://blog.csdn.net/hello_hwc/article/details/78317863,黄文臣

你可能感兴趣的:(iOS客户端启动优化)