前言
这里主要剖析一下一个 App 从点击图标, 到展现首页的整个过程.
App 是如何启动的
按顺序划分
加载可执行文件 (读取 Mach-O)
加载动态库 (Dylib)
- Rebase & Bind
- Objc
- Initializers
- --------- main() ---------
执行 AppDelegate 的代理方法 (如:
- didFinishLaunchingWithOptions
- ).
根据业务注册 SDK, 获取数据库数据等.
初始化 Windows, 初始化 ViewController.
加载可执行文件 (读取 Mach-O)
Apple 操作系统使用 dyld 加载可执行文件.
dyld 全程为 dynomic loader, 作用是加载一个进程所需要的 Image, 在 opensource-apple https://opensource.apple.com 可以找到它的开源代码.
加载动态库 (Dylib)
dyld 读取完 Mach-O 的 Header 和 Load Commands 后, 就会找到可执行文件的依赖动态库. 接着 dyld 会将所依赖的动态库加载到内存中. 这是一个递归的过程, 依赖的动态库可能还会依赖别的动态库, 所以 dyld 会递归每个动态库, 直至所有的依赖库都被加载完毕.
加载后的动态库会被缓存到 dyld shared cache 中, 提高读取效率.
简单的说下 Mach-O, 简单的可以分为三个部分, Header,Load Commands,Segment Data.
Header 中包含的是可执行文件的 CPU 架构, Load Commands 的数量和占用空间.
Load Commands 中包含的是 Segment 的 Header 与内存分布, 以及依赖动态库的版本和 Path 等.
Segment Data 就是 Segment 汇编代码的实现, 每段 Segment 的内存占用大小都是分页页数的整数倍.
Rebase & Bind
这两个过程合在一起说, 是因为他们之间的工作是相互补充的.
Apple 为了解决应用安全, 用到了 ASLR(Address space layout randomization 地址空间布局随机化) 和 Code Sign.
App 被启动后, 会被映射到虚拟内存中, 这样 App 在这个空间中就有了一个起始地址, 但这个起始地址是固定的. ASLR 能使这个起始地址随机化, 这项技术可以防止攻击者通过初始地址 + 偏移量的方法找到函数的内存地址.
Code Sign 就是签名, 在进行加密的时候, 会对每一个 Page(这里指的是 Segment Data) 都进行加密, 当 dyld 进行加载的时候, 会对每一个 Page 都进行独立的验证.
Mach-O 中采用了 PIC(Position Independent Code 地址无关代码), 大当我们在调用函数时, 会在__Data 段中建立一个指向该函数的指针, 通过这个指针来间接调用.
Mach-O 中有很多符号, 有些指向当前 Mach-O 的 (我们为 App 编写的代码), 有些指向其他 DyLib(依赖的动态库).
Rebase 的作用是重新修正指向当前 Mach-O 指针的指向, 因为上面提到的 ASLR 将地址随机化, 起始地址不在是固定的, 重新修复后, App 才能正常运行.
Bind 的作用是重新修复外部指针的指向, 这个过程会根据字符串匹配的方式来查找符号表, 比起 Rebase 会略慢 (这里 fishhook 的实现基础, 它在 dyld 绑定 C 库的时候进行了 hook).
Objc
因为 Objective C 的动态特性, 所以在 Main 函数执行之前, 需要把类信息注册到一个全局 Table 中. 同时, Category 的方法也会被注册到对应类中, Category 中的同名方法实现, 会根据编译顺序, 被最后一个编译的 Category 实现所覆盖. 同时还会做 Selector 的唯一性检测.
Initializers
这个阶段是包含必要的初始化.
+load
C/C++ 静态初始化对象和标记有__attribute__(constructor) 的方法
这里区分下 + load 方法与 + Initialize 方法, 前者是在类加载时调用的, 后者是在类第一次收到 message 之前调用的.
main() 方法之后的事
这里就不做展开了, 都是我们亲手写的代码.
Dyld 3
以上我们介绍了 dyld2 的加载方式, 在 2017WWDC,Apple 推出了 Dyld3.
Dyld2 是从程序开始时才开始执行的, 而 Dyld3 则将 Dyld2 的一些过程进行了分解.
Dyld3 分为 out-of-process, 和 in-process.
out-process 会做:
分析 Mach-O Headers
分析以来的动态库
查找需要的 Rebase 和 Bind 的符号
将上面的分析结果写入缓存.
in-process 会做:
读取缓存的分析结果
验证分析结果
加载 Mach-O 文件
- Rebase&Bind
- Initializers
使用了 Dyld3 后, App 的启动速度会进一步提高
启动阶段的优化建议
减少动态库的数量, 推荐使用系统库.
减少类和方法的数量.
减少初始化函数.
尽量使用 Swift.
Swift 没有初始化器.
Swift 不允许特定类型的未对齐数据结构.
Swift 代码更精简.
想要了解 Dyld 的同学, 可以看看这篇文章 App 启动流程以及优化 WWDC 2017 https://www.jianshu.com/p/96f66b0c943c
参考资料
深入理解 iOS App 的启动过程
App 启动流程以及优化 WWDC 2017 https://www.jianshu.com/p/96f66b0c943c
来源: https://juejin.im/post/5c8e278d51882545b32e657f