iOS编译
当一个xcode工程build之后一般会执行如下几个步骤:
- 预处理
- 语法和语义分析
- 生成代码和优化
- 汇编
- 链接
iOS编译采用Clang作为编译器前端,LLVM作为编译器后端。流程如下
Clang的任务:预处理、词法分析、语法分析、语义分析、静态分析、生成中间代码。
预处理:以#开头的代码预处理。包括引入的头文件和自定义宏。
词法分析:每一个.m源文件的声明和定义从string转化为特殊的标记流。
语法分析:将标记流解析成一颗抽象语法树( abstract syntax tree-AST)。
静态分析:包含类型检查和其他检查。
中间代码生成:生成LLVM代码。
LLVM的任务:将代码进行优化并产生汇编代码。
汇编器:将可读的汇编代码转换为机器代码,最终创建一个目标对象.o文件。
链接器的任务:把目标文件和库相连,最终输出可运行文件:a.out。
Mach-O
Mach-O是针对不同运行时可执行文件的类型。
文件类型:
- Exectuable: 应用的主要二进制
- Dylib: 动态链接库
- Bundle: 不能被链接的Dylib,只能在运行时使用
dlopen()
加载
Mach-O镜像文件
所有Mach-O(可使用MachOView工具查看)都包含:__TEXT, __DATA, __LINKEDIT:
- __TEXT 包含Mach header, 被执行的代码和只读常量。
- __DATA 包含全局变量,静态变量。可读写。
- __LINKEDIT 包含加载程序的元数据,比如函数的名称和地址。
app启动
当用户点击一个app,app从启动到打开第一个页面的时间 t = t1 + t2,其中t1 = 系统dylib和自身app可执行文件(app中所有.o文件的集合)的加载,t2为main函数到appdelegate中的- (BOOL)Application:(UIApplication *)Application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
方法执行结束这段时间。
app启动后,先加载可执行文件,再使用dyld(动态链接器)动态递归加载dylib(系统的framework,oc runtime的libobjc,系统级别的libSystem)。由于dylib是共享的,所以可以减少app包的体积。
dylib加载过程
- load dylibs image
- Rebase image
- Bind image
- Objc setup
- initializers
Rebase/Bind是用来修复image中的资源指针,使其指向正确的地址(因为iamge是加载在虚拟内存中(ASLR))
Objc setup工作
- 注册Objc类
- 把category定义插入方法列表
- 保证每一个selector唯一
initializers
前面三步是静态调整,修改__DATA segment内容。这里开始动态调整,开始在堆栈中写入内容。
main()之前的加载时间
它可以通过以下方式来显示。
结果如下:
main()调用之后的加载时间
- 准备阶段,主要是图片的解码
- 布局阶段,
-(void)layoutSubViews()
- 绘制阶段,
-(void)drawRect:(CGRect)rect
- 启动阶段必要服务的启动、必要数据的创建和读取。
优化启动时间
- 内嵌的dylib尽可能少,或者合并起来。
- Rebase/Binding减少__DATA中需要修正的指针。 对于oc来说减少 class, selector, category 这些元数据的数量,对与c++来说,减少虚函数数量。swift结构体需要修正的比较少。
- 将不必须在
+load
中做的事延迟到+ initialize
中。 - 不使用xib,直接用代码加载首页视图。
- release版不要用NSLog输出。
- 启动时的网络请求尽可能异步。