iOS-main函数
写在前面
如果你面试过几个公司的情况下,面试官们比较喜欢问的一道题就是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 | /System/Library/Frameworks/CoreGraphics.framework/CoreGraphics (compatibility version 64.0.0, current version 1129.2.1) |
其中有两个比较特殊的,默认添加的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的顺序概括为下面
- 从kernel 留下的原始调用栈引导和启动自己
- 将程序依赖的动态链接库递归加载进内存,当然这里有缓存机制。
- non-lazy符号立即link到可执行文件,lazy的存表里
- Runs static initializers for the executable
- 找到可执行文件的main函数,准备参数并调用。
- 程序执行中负责绑定lazy符号,提供runtime dynamic loading services 提供调试器接口
- 程序main函数 return 后执行 static terminator
- 某些场景下main函数结束后调libSystem 的_exit函数
得益于 dyld 是开源的,github 地址,我们可以从源码一探究竟。