App启动时在main函数之前做了什么

App 启动时在执行 main() 之前做了什么

编译的几个主要过程,生成可执行文件

  • 首先,你写好代码后,LLVM 会预处理你的代码,比如把宏嵌入到对应的位置
  • 预处理完后,LLVM 会对代码进行词法分析和语法分析,生成 ASTAST 是抽象语法树,结构上比代码更精简,遍历起来更快,所以使用 AST 能够更快速地进行静态检查,同时还能更快地生成 IR(中间表示)
  • 最后 AST 会生成 IRIR 是一种更接近机器码的语言,区别在于和平台无关,通过 IR 可以生成多份适合不同平台的机器码。对于 iOS 系统,IR 生成的可执行文件就是 Mach-O

加载动态链接库

查看一个 APP 都使用了哪些动态库,包括系统自带的动态库和第三方动态库,这些动态库将会在动态链接过程中被加载

找到App可执行文件路径,通过 otool 命令

1
$ otool -L TestMain

-L 参数打印出所有 link 的 framework(去掉了版本信息如下)

1
2
3
4
5
6
TestMain:
/System/Library/Frameworks/Foundation.framework/Foundation
/usr/lib/libobjc.A.dylib
/usr/lib/libSystem.B.dylib
/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
/System/Library/Frameworks/UIKit.framework/UIKit

可以看到有两个默认添加的 lib: libobjc(objcruntime), libSystem 中包含了很多系统级别的 lib, 比如我们熟知的 libdispatch(GCD), libsystem_c(C语言库), libsystem_blocks(Block), libcommonCrypto(加密库,比如常用的 md5 函数)

这些 lib 都是 dylib 格式的(如 Windows 中的 dll), 使用动态链接有如下优点:

  • 代码共用: 很多程序都动态链接了这些 lib,但它们在内存和磁盘中中只有一份
  • 易于维护: 由于被依赖的 lib 是程序执行时才 link 的,所以这些 lib 很容易做更新,比如libSystem.dyliblibSystem.B.dylib 的替身,哪天想升级直接换成 libSystem.C.dylib 然后再替换替身就行了
  • 减少可执行文件体积:相比静态链接,动态链接在编译时不需要打进去,所以可执行文件的体积要小很多

dyld

dyld(the dynamic link editor), 动态链接器,概述 dyld 做了如下几件事:

  • 先执行 Mach-O 文件,根据 Mach-O 文件里 undefined 的符号加载对应的动态库,系统会设置一个共享缓存来解决加载的递归依赖问题
  • 加载后,将 undefined 的符号绑定到动态库里对应的地址上
  • 最后再处理 +load 方法,main 函数返回后运行 static terminator

ImageLoader

将文件加载进内存,且每一个文件对应一个 ImageLoader 实例来负责加载。

  • 在程序运行时它先将动态链接的 image 递归加载 (也就是上面加载的递归依赖问题)
  • 再从可执行文件 image 递归加载所有符号

总结

  • dyld 开始将程序二进制文件(Mach-O)初始化
  • 交由 ImageLoader 读取 image,其中包含了我们的类、方法等各种符号
  • 由于 runtimedyld 绑定了回调,当 image 加载到内存后,dyld 会通知 runtime 进行处理
  • runtime 接手后调用 map_images 做解析和处理,接下来 load_images 中调用 call_load_methods 方法,遍历所有加载进来的 Class,按继承层级依次调用 Class+load 方法和其 Category+load 方法

问题

Q: 重载自己 Class+load 方法时需不需要调父类?

A: runtime 负责按继承顺序递归调用,所以不能调 super

Q: 在自己 Class+load 方法时能不能替换系统 framework(比如 UIKit)中的某个类的方法实现

A: 可以,因为动态链接过程中,所有依赖库的类是先于自己的类加载的

Q: 重载 +load 时需要手动添加 @autoreleasepool 么?

A: 不需要,在 runtime 调用 +load 方法前后是加了 objc_autoreleasePoolPush()objc_autoreleasePoolPop() 的。

Q: 想让一个类的 +load 方法被调用是否需要在某个地方 import 这个文件?

A: 不需要,只要这个类的符号被编译到最后的可执行文件中,+load 方法就会被调用(Reveal SDK 就是利用这一点,只要引入到工程中就能工作)

top