一款 iOS App 的启动时长对于用户体验来说是至关重要的因素,也是各大公司努力优化的点。最近看了2016年WWDC的一篇视频,怕日后忘记,做一些小总结。
启动过程:
一、加载dyld到App进程
什么是dyld?
dyld的全称是dynamic loader,它的作用是加载一个进程所需要的image。这里提到的image并不是我们认知的意思,指的是Executable,Dylib或者Bundle的一种。
此时内核对dyld都做了哪些事?
首先内核将App的执行文件加载到随机地址空间,之后内核将dyld的执行文件加载到随机地址空间,最后内核去执行执行dyld文件。
二、加载动态链接库
dyld拿到App的执行文件——mach-o文件后,首先从文件的header中解析出App依赖的dylib列表,找到每一个依赖的dylib。之后打开并读取dylib文件的起始位置,验证签名,确保dylib没有被篡改。验证签名后,对dylib中的每个segment调用mmap()。加载完一个dylib后,接着检查这个dylib所依赖的dylib,就这样的递归加载,直到所有的dylib加载完毕。通常一个App所依赖的动态库在100-400个左右,其中大多数都是系统的动态库,它们会被缓存到dyld shared cache,这样读取的效率会很高。
三、Rebase && Bind
首先先讲解一个概念——ASLR
ASLR:全称是Address space layout randomization,翻译过来就是“地址空间布局随机化”。
App被启动的时候,程序会被影射到逻辑的地址空间,这个逻辑的地址空间有一个起始地址,而ASLR技术使得这个起始地址是随机的。如果是固定的,那么黑客很容易就可以由起始地址+偏移量找到函数的地址。
为什么需要Rebase 和 Bind?
是因为刚刚提到的ASLR使得地址随机化,导致起始地址不固定,App和每个dylib加载到的都是随机地址空间,代码中原来的函数地址跟真实的函数地址会有差异。修复这个差异的过程就是rebasing和binding。
Rebase:Rebasing过程就是从__LINKEDIT取出函数指针,根据偏移量修改函数指针,存入__DATA中,Rebase解决了内部的符号引用问题。
Binding:当引用动态库其他的函数或者变量时,当前mach-o文件会指向其他dylib。这时候就需要Binding操作,dyld会根据符号表去找到相应函数和变量地址,Binding解决了修正外部指针指向的问题。
三、Objc Setup
Objective C是动态语言,为了维持它的动态性,在启动时,所以在执行main函数之前,需要把类的信息注册到一个全局的Table中。同时,Objective C支持Category,基于runtime的特性,在初始化的时候,也会把Category中的方法插入类结构体的方法列表中。
四、Initializers
完成objc的相关工作之后,需要完成动态库一些初始化工作:包括了执行 +load() 方法、attribute((constructor)) 修饰的函数的调用、创建 C++ 静态全局变量。运行“自下而上”,这样每个初始化器都可以调用它下面的dylibs,最后,Dyld在可执行文件中调用main()。
结语:至此iOS App只mian函数执行之前所做的事情就简单的介绍完了,如有错误的地方,欢迎大家踊跃提出。