NSObject的+load和+initialize详解

通过runtime源码解析load和initialize

+load

通过调用堆栈,我们可以看出系统首先调用的是load_images方法

load_images

prepare_load_methods

这个方法主要是两个核心方法schedule_class_load和add_category_to_loadable_list主要干了两件事 1、获取了所有类后,遍历列表,将其中有+load方法的类加入loadable_class; 2、获取所有的类别,遍历列表,将其中有+load方法的类加入loadable_categories. 接下来让我们看看schedule_class_load

从以上代码可以看出在加载类的load方法的时候,首先是先将父类加入到loadable_class,之后才是子类。所以保证了父类一定是在子类先调用!

call_load_methods

call_load_methods循环遍历,首先是调用了class的load方法,然后调用了category的方法 接下来让我们看看call_class_loads的代码实现

核心方法是 (load_method)(cls, SEL_load),typedef void(load_method_t)(id, SEL);可以看到load_method是一个函数指针,所以是直接调用了内存地址!

+initialize

一样我们通过调用堆栈可以看到系统调用的是_class_initialize方法

_class_initialize

核心方法就是callInitialize接下来让我们看看该方法的实现

callInitialize

通过该实现我们可以看出其实initialize的本质就是objc_msgSend,所以遵循消息的转发机制。

示例演示

Animal 父类

Dog 子类

  • 未注释代码时,打印结果

    2017-07-31 14:24:47.671 test[53134:5737864] Animal load

    2017-07-31 14:24:47.809 test[53134:5737864] Dog load

    2017-07-31 14:24:48.112 test[53134:5737864] Animal initialize

    2017-07-31 14:24:48.112 test[53134:5737864] Dog initialize

  • 注释掉代码时,打印结果

    2017-07-31 14:39:03.916 testaa[53659:5814782] Animal load

    2017-07-31 14:39:03.917 testaa[53659:5814782] Dog load

    2017-07-31 14:39:03.966 testaa[53659:5814782] Animal initialize

    2017-07-31 14:39:03.967 testaa[53659:5814782] Animal initialize

通过两次打印看到以下现象:

  • 第一次测试load initialize各打印了一次,并且load比initialize提前打印

    原因:+ load是应用一启动就调动,+ initialize是我们调用该类方法的时候才会调用,而且这两个方法理论上只会调用一次。

  • 第二次测试Animal的initialize打印了两次

    原因:initialize遵循的是objc_msgSend消息的转发机制,第一次打印是因为我们实例化了Animal并且调用了方法;第二次打印是因为Dog子类没有实现该方法,根据消息转发机制的原理,所以会向上查找父类是否实现了该方法,所以调用了父类的initialize的方法了。所以父类的initialize可能会被调用多次所以建议以下写法:

我们可以做个以下尝试Dog不继承Animal,然后在Compile Source中将Dog的顺序拖到Animal之前。 如下图:

可以观察到Dog的Load在Animal之前打印。所以在没有继承关系的时候Load的调用顺序跟我们的Compile Source的排列顺序有关。有继承关系的,父类一定比子类先调用

结论

  • +load方法是在程序一启动的时候就会调用,并且在main函数之前,是根据Xcode中Compile Sources的顺序调用的。其内部本质是通过函数内存地址的方式实现的。所以在有继承关系的时候子类与父类没有任何关系,不会相互影响。

  • +initialize方法是我们在第一次使用该类的时候即调用某个方法的时候系统开始调用 ,是一种懒加载的方式。其内部本质是通过objc_msgSend发送消息实现的,因此也拥有objc_msgSend带来的特性,也就是说子类会继承父类的方法实现,而分类的实现也会覆盖元类。

Last updated