+load 方法是如何被调用的

开始

在随便一个 +load 方法打个断点: -w250 通过堆栈可以知道,load 方法调用栈的栈底是 _dyld_start 方法,XUN 将 Mach-O 映射到内存后,就由内核态切换到了用户态,而 _dyld_start 可以说就是用户态的开始, Objc runtime 的初始化就是由 dyld 调用的。

为了更顺畅地阅读,以下所有提到的方法在图中都会用红色的框框出来。 以下代码来自:GitHub - opensource-apple/objc4

注册 load_image 方法

在 Objc runtime 初始化时,在 _objc_init 方法中会把 load_image 方法注册成回调,每当有新的 image 被 map 到 runtime 之后,都会执行这个回调方法并传入最新 image 的信息列表 infoList,load_image 主要做的事就是调用当前 image 中的 load 方法 -w250

load_image

  1. 先检索 image 是否有 load 方法
  2. load_images_nolock 找到所有的 load 方法
  3. call_load_methods 调用所有的 load 方法 -w350

load_images_nolock

如果找到了 image 的 Load 地址,调用 prepare_load_methods 方法 -w250

prepare_load_methods:

  1. 递归地找到所有 class 中 load 方法,并加入 loadable_classes_list 中
  2. 找到所有 category 中的 load 方法,并加入 loadable_categories_list 中(注意 category 就没有递归逻辑了) -w250

其中 schedule_class_load 方法递归将有 load 的类加入 list 中 -w250

add_class_to_loadable_list,add_category_to_loadable_list 两个是差不多的思路: 获取 load 的方法指针,加入对应数组中,下标++ -w250

getLoadMethod:获取 load 方法指针,遍历类的所有方法,找到方法名字和 “load” 字符串匹配的方法,返回其方法指针 -w250

call_load_methods

接下来到了整个流程的第 3 大步骤,调用所有步骤 2 找到的 load 方法 这个方法大体能理解,但是不太理解为什么 call_class_loads 要循环调,call_category_loads 只能调一次 -w250

call_class_loads: 遍历 loadable_classes,找到每个 class 的 load 方法,执行 -w250

过程图示

-w250

由这个过程可知

  1. Load 方法是直接调用函数指针,不会执行 objc_msgSend 的那一整套流程,与 initialize 不同
  2. category 也是可以实现 load 方法的
  3. class 的 load 先调用,category 的 load 后调用
  4. class 会递归着先调用父类 load 方法,再调用子类 load 方法;category 不存在这样的逻辑