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作用顺序概括:

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

dyld是开源的,源码github地址

在项目源码中有dyldStartup.s这个汇编文件,实现了__dyld_start,其主要实现了:

  1. 调用dyldbootstrap::start()方法
  2. 上个方法返回了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去解析这个二进制文件的符号表和代码

 屏幕快照 2019-04-11 14.28.40

整个调用顺序:

  1. dyld将整个应用程序二进制文件初始化
  2. 由ImageLoader读取image,其中包括了代码、类和方法各种符号
  3. 因为runtime向dyld绑定了回调,当image加载到内存后,dyld会通知runtime处理
  4. 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这个分支的初始化以及所做的事情,真正流程比这个复杂的多,还有类似GCDXPC等系统库分支