ObjC load与initialize 简析

请注意,本文编写于 320 天前,最后修改于 191 天前,其中某些信息可能已经过时。

+ (void)load

每个类,每个分类中都存在一个+(void)load方法。我们不需要显式的进行调用,当runtime动态加载类、分类的时候会进行调用。

实例文件目录
实例文件目录

创建一个command line 项目 创建几个类。Student继承自Person,每个类包括其Category中都实现load方法,运行我们发现控制台打印了以下信息。

load信息
load信息

我们发现类中的load方法没有被Category中的load方法覆盖,而是全都进行了调用。这是为什么呢?另外load方法的调用顺序有什么规律呢?

runtime 源码-窥探load方法的调用

通过runtime源码我们可以看到在runtime的入口函数void _objc_init(void)(存在于objc_os.mm中)中一段代码加载images(镜像)。_dyld_objc_notify_register(&map_images, load_images, unmap_image);

进入load_images可以发现有调用load方法的函数

load_images
load_images

通过函数名可以知道这里实现对load方法展开调用。查看该方法的实现,我们发现它又是通过call_class_loads对类的load方法进行调用,通过call_category_loads对category中的load方法进行调用的。它使用了一个while循环首先调用类的load方法,类的load方法全部调用完成之后,再调用Category的load方法。

查看call_class_load函数的实现,我们发现它通过遍历一个数组获取一个结构体loadable_class。

loadable_class
loadable_class

这里的method就是load函数的地址,而call_class_loads获取函数地址,直接进行调用。call_category_loads也是如此。

那么loadable_classes这个list是按照什么规律生成的呢?

重新观察load_iamges函数,我们发现有一个prepare_load_methods函数

prepare_load_methods
prepare_load_methods

这个方法遍历classlist,通过schedule_class_load函数准备class的load方法。这个函数中有递归调用了自己,首先处理父类,最后调用了add_class_to_loadable_list函数,将load生成loadable_classes数组和loadable_classes_used可调用的load方法的数量。

add_class_to_loadable_list
add_class_to_loadable_list

通过这个函数的实现,我们也可以验证上文中所说的loadable_class中的method就是load方法,我们可以看到method是通过调用getLoadMethod获取并添加到结构体loadable_class中的。

Category的将load方法加载到数组的方法跟class基本一致,但是Category是按照编译顺序进行添加的。

+ (void)load的一些问题

综上所述:

  • +(void)load方法是在runtime加载类或分类的时候调用的。
  • 每个类分类的load在程序运行过程中只会调用一次。调用的完成loadable_classes_used数量会置为0。
  • 先调用类的load方法(先编译的先调用)父类load优先调用
  • 分类的load方法 先编译的先调用
  • load方法是直接查找到函数地址进行调用的而不是通过消息发送机制调用的,所以不会出现方法覆盖

+(void)initialize

initialize在一个类刚刚接收到消息的时候进行调用。利用上面的代码,实现initialize方法,在main函数中让student给alloc发消息。得到以下打印:

initialize信息
initialize信息

经过上面的经验可以得知,initialize是通过objc_msgSend方法调用的,所以只打印了category的initialize中的日志。而category的调用,根据Category的底层实现可以得知,后编译的被调用了。

runtime 源码-窥探initialize方法的调用

由于objc_msgSend的实现没有开源,所以通过查看objc-runtime-new.mm中的获取实例方法的函数(class_getInstanceMethod)进行窥探。

在该方法中通过调用lookUpImpOrNil方法查找方法的实现,这个方法又调用了lookUpImpOrForward方法继续查找。

lookUpImpOrForward方法中如果存在initialize并且没有调用过,则通过_class_initialize (_class_getNonMetaClass(cls, inst));调用initialize。

_class_initialize
_class_initialize

_class_initialize (_class_getNonMetaClass(cls, inst));中通过递归先通过callInitialize(cls);调用父类的initialize。

callInitialize(cls);中就可以看出是通过objc_msgSend调用的。

调用objc_msgSend
调用objc_msgSend

所以会执行通过isa指针查找方法实现的过程。

+(void)initialize的一些问题

  • +(void)initialize是通过objc_msgSend调用的
  • category实现了initialize方法会覆盖父类的initilize方法
  • initialize也只初始化一次,但是由于是通过objc_msgSend的调用的,如果子类没有实现initialize,而父类实现了,则子类每次初始化的时候都会调用父类的+(void)initialize,所以父类的+(void)initialize方法会被调用多次。
Comments

添加新评论