03·iOS 面试题·main()之前的过程有哪些?

作者: pengxuyuan | 来源:发表于2018-09-11 00:01 被阅读10次

    前言

    一个 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 过程,相关的知识点基本都是从他的博客中学习的。

    参考文献中的博客也是很不错的,希望大家会喜欢。

    参考文献

    iOS 程序 main 函数之前发生了什么

    iOS App 启动时间测量

    iOS开发 APP启动main()调用之前的加载过程

    如何精确度量 iOS App 的启动时间

    iOS App程序在调用main()之前做了那些事情?

    相关文章

      网友评论

        本文标题:03·iOS 面试题·main()之前的过程有哪些?

        本文链接:https://www.haomeiwen.com/subject/uofygftx.html