你过去可能和笔者一样听说过,对于 load
方法的调用顺序有三条规则:
main
函数之前由Objectvie-C的runtime调用- 父类先于子类调用
- 类先于分类调用
首先写点代码测试一下上面的3条规则:
@implementation ALin
+ (void) load {
NSLog(@"Alin ===== load");
}
@end
@implementation ALin (hi)
+ (void)load {
NSLog(@"ALin+hi === load");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"main");
return 0;
}
}
在main.n函数中仅仅是打印了一个main,没有调用任何的ALin这个类的任何+load,但是还是执行了ALin类实现的和ALin+hi分类中的+load.验证上面的三条规则确实没错.
![有帮助的截图]({ { site.url }}/assets/postsImages/load_ALin.png)
可是load
方法是究竟如何被调用的,+ load
方法为什么会有这种调用顺序呢?这些问题值得考究,首先来通过 load
方法的调用栈,分析一下它到底是如何被调用的。
因为 ObjC 的 runtime 只能在 Mac OS 下才能编译,所以文章中的代码都是在 Mac OS,也就是 x86_64 架构下运行的,你可以在objc-runtimeclone 整个仓库来进行调试。
在+ load中添加一个断点
![有帮助的截图]({ { site.url }}/assets/postsImages/load_stop_0.jpg)
左侧的调用栈很清楚的看到哪些方法被调用了:
0 +[ALin load] 1 call_class_loads() 2 call_load_methods 3 load_images 4 dyld::notifySingle(dyld_image_states, ImageLoader const*) 11 _dyld_start
dyld 是 the dynamic link editor 的缩写,它是苹果的动态链接器,又它来加载程序的可执行文件
每当有新的镜像加载之后,都会执行 3 load_images
方法进行回调,在进入load_images
函数打下断点,查load_images
看的调用栈。
![有帮助的截图]({ { site.url }}/assets/postsImages/load_load_images.jpg)
看了dyld_start之后进入了_objc_init:
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
在这个函数调用_dyld_objc_notify_register注册了一个监听dyld加载image的函数,进入map_images:
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
rwlock_writer_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
随后发现最调用了_read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)去读取并检查所有的文件的内容,_read_images函数特别长,检查所有的header_info信息,针对不同的内容信息进行对应的处理,这里取一部分比较关心的内容:
Realize non-lazy classes
如果发现有+load方法的类的定义,或者static instances就会调用realizeClass(cls);对类进行初始化:
realizeClass(cls);是关注点
// Realize non-lazy classes (for