原文链接

参考

写在前面

如果你面试过几个公司的情况下,面试官们比较喜欢问的一道题就是main函数之前程序都干了什么,或者怎么优化App启动。其实都是一个问题,就是想问问,main函数之前的执行过程看你是不是清晰。大多数的情况下,我们都知道main函数是程序的入口,我们所有的操作也都是在AppDelegate里面的window显示之后,而不去关心之前程序都发生了什么。那么今天就来梳理一下main函数之前一步步程序是怎么走的。

dyld

新建main-test 项目,编译找到模拟器的,main-test.app 先后显示包内容,通过 otool命令

1
kang-mac:main-test.app meishi$ otool -L main-test

能够打印出隐含link的framework。

1
2
3
4
5
6
7
8
9
10
11
/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics (compatibility version 64.0.0, current version 1129.2.1)

/usr/lib/libsqlite3.dylib (compatibility version 9.0.0, current version 274.6.0)

/System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1450.14.0)

/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)

/usr/lib/libSystem.dylib (compatibility version 1.0.0, current version 1252.0.0)

/System/Library/Frameworks/UIKit.framework/UIKit (compatibility version 1.0.0, current version 3698.33.6)

其中有两个比较特殊的,默认添加的lib: libobjc(objc 和 runtime) 和 libsystem(系统级别的)

  • libdispatch(GCD)
  • libsystem_c(C语言库)
  • libsystem_blocks(Block)
  • libcommonCrypto(加密库,比如常用的md5函数)

这些lib 都是dylib格式的,系统使用动态链接的好处如下:

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

dyld是动态链接器(the dynamic link editor),系统kernel做好启动程序的初始准备后,交给dyld负责, 这篇文章https://www.mikeash.com/pyblog/friday-qa-2012-11-09-dyld-dynamic-linking-on-os-x.html对dyld的顺序概括为下面

  1. 从kernel 留下的原始调用栈引导和启动自己
  2. 将程序依赖的动态链接库递归加载进内存,当然这里有缓存机制。
  3. non-lazy符号立即link到可执行文件,lazy的存表里
  4. Runs static initializers for the executable
  5. 找到可执行文件的main函数,准备参数并调用。
  6. 程序执行中负责绑定lazy符号,提供runtime dynamic loading services 提供调试器接口
  7. 程序main函数 return 后执行 static terminator
  8. 某些场景下main函数结束后调libSystem 的_exit函数

得益于 dyld 是开源的,github 地址,我们可以从源码一探究竟。