main函数之前发生了什么
学习自sunnyxx的iOS 程序 main 函数之前发生了什么
dyld
动态链接库
iOS中用到系统frameWork都是动态链接的
使用otool
命令查看依赖的动态库
otool -L TestMain
TestMain:
/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics
/System/Library/Frameworks/UIKit.framework/UIKit
/System/Library/Frameworks/Foundation.framework/Foundation
/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
/usr/lib/libobjc.A.dylib /usr/lib/libSystem.B.dylib
除了我们添加或依赖的系统动态库,还有默认添加的lib:libobjc
(objc和runtime需要依赖的库),libSystem
包含了很多系统界别的lib,常用到的有:
- libdispatch(GCD)
- libsystem_c(C语言库)
- libsystem_blocks(Block)
- libcommonCrypto(加密库,比如常用的md5函数)
这些库都是dylib
格式
dyld(the dynamic link editor)
Apple的动态链接器,系统kernel做好启动程序的准备后由dyld负责,dyld作用顺序概括:
- 从kernel留下的原始调用栈引导和启动自己
- 将程序依赖的动态链接库递归加载进内存,当然这里有缓存机制
- non-lazy符号立即link到可执行文件,lazy的存到表里
- 为可执行文件执行静态初始化
- 找到可执行文件的main函数,准备参数并调用
- 程序执行中 绑定lazy符号、提供runtime动态加载、提供调试器的接口
- 程序main函数return后执行static terminator
- 某些场景下 main函数结束后调用libSystem的__exit函数
dyld是开源的,源码github地址
在项目源码中有dyldStartup.s
这个汇编文件,实现了__dyld_start
,其主要实现了:
- 调用dyldbootstrap::start()方法
- 上个方法返回了main函数地址,填入参数并调用main函数
栈底的dyldbootstrap::start()方法,继而调用了dyld::_main()方法,其中完成了刚才说的递归加载动态库过程,由于libSystem默认引入,栈中出现了libSystem_initializer、libdispatch_init、_objc_init等的初始化方法。
ImageLoader
image指二进制文件,里面是各种符号和代码等,因此ImageLoader是将这些文件加入内存
runtime与load
libSystem
是是若干系统lib的集合容器,里面就一个init.c
文件,因此调用libSystem_initializer
会逐步调用到_objc_init
,这个就是objc和runtime
的初始化入口
这里除了初始化runtime环境外,_objc_init绑定了新image被加载后的callback
:
dyld_register_image_state_change_handler(
dyld_image_state_bound, 1, &map_images);
dyld_register_image_state_change_handler(
dyld_image_state_dependents_initialized, 0, &load_images);
当新的image被加载进来之后由runtime
去解析这个二进制文件的符号表和代码
整个调用顺序:
- dyld将整个应用程序二进制文件初始化
- 由ImageLoader读取image,其中包括了代码、类和方法各种符号
- 因为runtime向dyld绑定了回调,当image加载到内存后,dyld会通知runtime处理
- runtime调用回调函数
map_image
做解析处理,之后load_images
调用call_load_methods
方法,遍历所有加载的class,然后按照继承和层架关系调用Class和其Category的+load()
方法
load方法收集
在loadImage
后 会调用prepare_load_methods
方法来获取所有类的列表,并且收集其中load方法。这个方法中会调用schedule_class_load
遍历类递归至根节点来收集完成的关系树,并执行add_class_to_loadable_list
将其加入执行列表, 然后调用call_load_methods
方法,其会创建一个autoreleasePool
使用函数指针来动态调用类和Category
的+load
方法
此时,可执行文件中和动态库所有符号Class、Protocol、Selector、IMP...
等都已经按照格式加载到内存,并被runtime管理,这样之后,runtime
运行时机制和那些方法(动态添加class、swizzle等)才能生效
真正的main函数
当前面的这些操作结束,dyld会清理现场,将调用栈回归,调用真正的main函数
补充
此处只提及了runtime这个分支的初始化以及所做的事情,真正流程比这个复杂的多,还有类似GCD
XPC
等系统库分支