文章比较长,第一部分通过runtime的源码介绍了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带来的特性,也就是说子类会继承父类的方法实现,而分类的实现也会覆盖元类。
我们常用Method Swizzling建议一定要在Load方法中实现。
stackoverflow使用场景讨论