03·iOS 面试题·main()之前的过程有哪些?
前言
一个 App 的启动时间从侧面可以反映出这个 App 的质量。启动时间一般分为两部分:Pre-main Time 和 Loading Time。Loading Time 指的是程序入口 main 函数到 AppDelegate 的 applicationDidBecomeActive
方法之间的耗时,这个时间比较好测量:我们可以利用 NSDate
或者 CFAbsoluteTimeGetCurrent
等计时方法来计算两个函数之间的耗时。(具体可以看 Demo: PXYStartupTimeMonitor )
但是,Pre-main Time 相对于 Loading Time 就比较神秘,没有那么直观好测量。它是指我们点击 App 图标到程序入口 main 函数之间的耗时,这篇我们就来聊一下 Pre-main 这个过程有什么步骤,以及我们需要注意的点。
Pre-main 的大概过程
Pre-mian 大概过程主要分为:Load dylibs、Rebase、Bind、Objc、Initializers 这几个步骤。
当我们点击 App 图标,内核就开始做启动程序的初始化,然后交给 dyld(The Dynamic Link Editor,动态链接器);dyld 首先会读取镜像文件,然后递归的查找动态库,利用 ImageLoader 来将其加载到内存中,但是由于 ASLR 的特性,这里需要 Rebase/Bind 修复镜像中的资源指针,来指向正确的地址;然后 dyld 会通知 Runtime:ImageLoader 已经将对应的镜像加载到内存,这个时候 Runtime 会调用 map_image 去解析和处理该镜像资源(譬如注册 Objc 类、处理 category 等);接下来再调用 load_image,遍历调用类的 load 方法、调用C++的构造函数属性函数、创建非基本类型的C++静态全局变量等等。
经过这些过程,最终才会调用到我们程序入口 main 函数,再接下来就是我们的 Loading Time 了。
Pre-main 优化方向
通过上面简述,我们大概知道 Pre-main 的过程,我们可以分别针对不同的步骤进行优化。
优化 Load Dylibs Image 过程
尽量减少动态库的依赖和合并一些动态库,减少 ImageLoading 加载镜像的数量;不过这里一般不是 App 的瓶颈,这里只需要平时需要注意项目中不要依赖太多动态库就可以了。
优化 Rebase/Bind 过程
这里主要由于 ASLR 特性,需要修复镜像中的资源指针,来指向正确的地址;这里一般不太好优化,只能说尽量将项目中不用的类给删除,减少类数量和 selector 数量。
优化 Objc 过程
这个过程主要是:注册 Objc 类,处理 category,将 category 中的方法属性协议等插入到本类中去,这块一般也没什么优化的空间。
优化 Initializers 过程
这个过程主要做初始化工作,相对其它过程会比较耗时,这里我们需要注意以下操作的耗时,去分析对应操作有没必要在 Pre-main 这个阶段去处理,是否可以在程序启动完成之后再处理:
- Load 函数中的操作
- C++的构造函数属性函数
- C++静态全局变量
通过上面几个步骤,我们主要能优化的点如下:
- 减少动态库的依赖
- 减少 Objc 类和方法
- 减少 C++的构造函数属性函数
- 减少 C++静态全局变量
- 减少 +load 函数数量,已经避免做耗时操作
Pre-main Time 如何测量
Xcode 自带环境变量
可以通过配置 Xcode 的环境变量来观察 Pre-main Time,这里苹果是推荐控制在 400 ms 以内,以下两张图是引用参考文献 Blog 的配图:
参考文献中 Blog 图片 参考文献中 Blog 图片更多环境变量的配置可以查看官方文档:Logging Dynamic Loader Events
Hook 方法统计耗时
通过对 Pre-main 过程的分析,我们可以优化以及统计耗时的基本在 Initializers 过程,这个过程主要是 Load 方法、C++的构造函数属性函数、C++静态全局变量;
统计 Load 方法耗时
对于统计 Load 方法,我们知道动态库是最早加载的,并且在 Initializers 过程的前面,我们可以创建一个动态库 Hook 所有类的 Load 方法,然后通过打点来计算 Load 方法的耗时,从而统计出 Load 方法的耗时,具体代码可以看: PXYAPMLoadMonitor
统计 C++ static initializers 耗时
对于统计 C++的构造函数属性函数、C++静态全局变量 暂时还没学会,大家可以可以看这篇 Blog:Hook所有+load方法(包括Category)、一种 hook C++ static initializers 的方法
总结
App 启动时间是有一个上限的,如果超过 15 秒,App 就会被 kill 掉。最近有个 App 上架被拒了,虽然启动时间没有超过 15 秒,但是也到了可耻的 8 秒 - -/… 所以 App 启动时间也是我们需要关注的一部分。
对于启动时间的相关知识,推荐一个博主:everettjf,里面有很多性能优化的博客。我本身也不了解 Pre-mian 过程,相关的知识点基本都是从他的博客中学习的。
参考文献中的博客也是很不错的,希望大家会喜欢。