OC底层原理33:启动优化(二)优化建议

张建 lol

冷启动和热启动

启动的过程一般是指 从用户点击APP图标进入到APP页面 的过程,一般在iOS开发中分为 冷启动热启动

  • 冷启动:用户将 设备重启 或者是 手动kill掉APP进程,又或者是 APP长时间未打开过,当用户点击启动APP图标的过程,这时需要创建一个新的 进程 分配给APP,并且内存中 不包含任何数据,必须从磁盘中载入数据到内存中的过程。

  • 热启动:用户在使用APP过程中,按下 Home键,APP不会立即被kill掉,而是存活一段时间,这段时间里用户再打开APP,此时APP还在退到后台前的状态,APP进程还在系统中,无需开启新的进程,APP也不会重新加载数据到内存中的过程。

启动优化概念

  1. WWDC 2016 中首次出现了APP启动优化的话题,其中提到:
  • App启动最佳速度是400ms以内 ,因为从点击App图标启动,然后 Launch Screen 出现再消失的时间就是400ms。

  • App启动最慢不得大于20s ,否则进程会被系统杀死

  1. 我们这里所说的启动优化,一般是指 冷启动 情况下的启动优化,是指用户唤起App开始到AppDelegate 中的 didFinishLaunchingWithOptions 方法执行完毕为止,并以 main() 函数为分界点,分为 pre-mainmain() 两个阶段:
  • pre-main 阶段:是指从用户唤起App到 main() 函数执行之前的过程

  • main() 阶段:是指 main() 函数开始执行到 didFinishLaunchingWithOptions 方法执行结束的过程

  1. 所以,pre-main() + main() 的过程就是从用户点击App图标到用户能看到主界面的过程,即需要优化的部分。

pre-main()启动优化

  1. 我们在前面 OC底层原理15:dyld源码分析 中,已经了解了 dyld 的加载流程,pre-main() 阶段的 启动时间 就是 dyld加载过程的时间

  2. 针对 pre-main() 阶段的启动时间,苹果提供了APP启动时间检测方法,在 Product -> Scheme -> Edit Scheme -> Environment Variables 点击 + 号添加环境变量 DYLD_PRINT_STATISTICS 设为 1,如下图:

  1. 然后运行,以下是 手机型号iPhone11 14.4.2 启动的我目前正在开发的电商项目的 pre-main() 时间:

  1. 如果想要更加详细的信息,就设置 DYLD_PRINT_STATISTICS_DETAILS1,得到以下信息:

说明
1 pre-main 阶段总共用时 1.3s
2 dylib loading time (动态库耗时):主要是加载动态库,用时 336.24ms
3 rebase/binding time (偏移修正/符号绑定耗时),耗时 61.26ms
3.1 rebase(偏移修正):任何一个App生成的二进制文件,在二进制文件内部所有的方法、函数调用,都有一个地址,这个地址是在 当前二进制文件中的偏移地址。一旦在运行时刻(即运行到内存中),每次系统都会 随机分配一个ASLR(Address Space Layout Randomization,赋值空间布局随机化)地址值(是一个安全机制,会分配一个随机的数值,插入在二进制文件的开头),例如,二进制文件中有一个test方法,偏移值是 0x001,而随机分配的 ASLR0x1000,如果想访问test方法,其内存地址(即真是地址)变为 ASLR+偏移值 = 运行时确定的内存地址(即0x1000 + 0x0001 = 0x1001)
3.2 binding(绑定):例如 NSLog方法,在编译时期 生成的 mach-o文件中,会创建一个符号 !NSLog(目前指向一个随机的地址),然后在运行时(从磁盘加载到内存中,是一个镜像文件),会将真正的地址给符号(即在内存中将地址与符号进行绑定,是 dyld 做的,也称为 动态库符号绑定),一句话概括:绑定就是给符号赋值的过程
4 Objc setup time(OC类注册的耗时):OC 类越多,越耗时
5 initializer time :(执行load和构造函数的耗时)

  1. 针对这几部,有以下几点优化建议:重点面试题
  • 尽量 少用外部动态库,苹果官方建议自定义的动态库最好 不要超过6个,如果超过6个,需要 合并 动态库

  • 减少 OC 类,因为类越多,越耗时

  • 将不必须在 +load 方法中做的事情延迟到 +initialize 中,尽量不要用 C++ 虚函数

  • 如果是Swift,尽量使用 struct

main 函数阶段的优化

  1. main 函数之后的 didFinishLaunching 方法中,主要是执行了各种业务,有很多并不是必须在这里执行的,这种业务我们可以采取 延迟加载,防止影响启动时间

  2. didFinishLaunching 中的业务主要分为三个阶段

  • 【第一类】初始化第三方sdk
  • 【第二类】App运行环境配置
  • 【第三类】自己工具类的初始化等
  1. main函数阶段的优化建议主要有以下几点:重点面试题
  • 减少启动初始化的流程,能懒加载的懒加载,能延迟的延迟,能放后台初始化的放后台,尽量少占用主线程的启动时间

  • 优化代码逻辑,去除非必须的代码逻辑,减少每个流程的消耗时间

  • 启动阶段能 使用多线程 来初始化的,就使用多线程

  • 尽量 使用纯代码 来进行UI框架的搭建,尤其是主UI框架,例如 UITabBarController,尽量避免使用Xib或者StoryBoard,相比纯代码而言,这种更耗时

  • 删除废弃类、方法

  • Post title:OC底层原理33:启动优化(二)优化建议
  • Post author:张建
  • Create time:2021-05-11 15:19:40
  • Post link:https://redefine.ohevan.com/2021/05/11/OC底层原理/OC底层原理33:启动优化(二)优化建议/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.
On this page
OC底层原理33:启动优化(二)优化建议