本文已参与[新人创作礼]活动,一起开启掘金创作之路。
启动流程
启动时间 = premain(t1) + main(t2)
t1 是dyld加载动态库dyldlib以及可执行文件的加载。
t2 是main调用之后到didfinsh方法完成并构建出第一个页面。
main函数之前的加载过程就是APP启动之后,首先加载可执行文件(自身APP所有.o文件的集合),然后开始加载动态链接库dyld,dyld是专门用来加载动态链接库的库,从dyld开始,dyld执行从可执行文件的依赖开始,递归加载所有依赖的动态库。 动态链接库包括:iOS中系统所用的framework ,加载 oc runtime 的libobjc,系统的libsystem,例如 libdispatich和libsystem_block。
无论对于系统的动态链接库还是APP本身的可执行.o文件都是镜像 image,他们都是以镜像为单位进行加载的,那么image 究竟包含哪些呢?
image:executable 可执行文件,比如.o文件。dylib 动态链接库 framework 就是动态链接库和相应的资源包含在一起的文件夹结构, bundle 资源文件只能用dlopen打开,不推荐使用这种方式加载。
系统使用动态链接库的好处,他是存放在共享缓存里,不占用APP的内存,被依赖的lib是在程序运行时菜连接的,相比静态库,不用打包到APP中,减少APP体积。
通过xcode配置DYLD_PRINT_STATISTICS 打印结果如下:
Total pre-main time: 31.70 milliseconds (100.0%)
dylib loading time: 27.29 milliseconds (86.0%)
rebase/binding time: 411015771.5 seconds (61552300.3%)
ObjC setup time: 78.12 milliseconds (246.4%)
initializer time: 74.97 milliseconds (236.4%)
slowest intializers :
libSystem.B.dylib : 6.10 milliseconds (19.2%)
libBacktraceRecording.dylib : 7.03 milliseconds (22.1%)
libobjc.A.dylib : 1.69 milliseconds (5.3%)
libMainThreadChecker.dylib : 57.82 milliseconds (182.3%)
. dylib loading : 动态库链接流程是dyld 加载动态库阶段包含系统的和自己写的动态库.
. rebase/binding : 符号绑定和指针指向修复阶段,就是bingingg和rebase.
. Objc setup : class 类注册、categary protocol 读取等
. initializer : 程序的初始化,包括所依赖的动态库的初始化 调用 load 函数,调用 C++ 析构函数等。
启动时间优化
动态链接库的加载过程分为5步
1. load dylib images 加载动态库镜像文件
在每个动态库加载的过程中,dyld需要 分析所依赖的动态库 、找到动态库的mach-o文件、打开文件、验证文件、在系统核心注册文件签名、对动态库的每一个segment调用mmap()
通常APP会加载100到400个dylibs,系统的动态库加载会被优化,可以很快的加载,针对这个步骤的优化有:减少非系统动态库的依赖,合并非系统库、使用静态资源,把代码加入主程序。
2. rebase/bind
由于aslr的存在,动态链接库和可执行文件在虚拟内存的加载地址每次启动时都不固定,所以需要这两步来修复资源指针的指向。rebase 修复的是指向当前镜像资源内部指针,bind 指向当前镜像资源的外部指针。
该阶段优化点有减少objc类的数量、减少selector数量、减少c++虚函数数量、使用swiftstruct 本质是是为了减少符号的数量
3. objc setup
主要工作:注册objc类、把category的定义插入方法列表、保证每个selector的唯一性
4. initializers
以上三步属于静态调整 ,都在修改__DATA segment 中的内容,从这开始动态调整开始在堆和堆栈中写入内容。主要工作:
objc 的+load函数、c++ 构造函数、非基本类型的c++静态全局变量的创建(类和结构体)
二进制重排
app 启动的时候在加载虚拟页进入内存时,总会有一些所想要的没有加载进来,导致page fault,重新加载page导致浪费时间,可以把启动所需要的内容放到最前面一段地址相连的内存中。这样会减少page fault的次数。
第一 可以通过改变conplier 文件编译顺序,但是作用不大
第二 可以设置.order文件 ,把启动所需要的符号放进去,但是这样操作成本太高,而且你不知道哪些是启动的时候所需要的函数和符号
第三 通过 clang 插装的方法,把启动时所需要的符号列出来