首发于我的个人博客
+load方法
创建类和分类
- 先创建类YZPerson类,然后创建它的两个分类
YZPerson.m类
1 |
|
YZPerson+test1.m类
1 |
|
YZPerson+test2.m类
1 |
|
创建完之后,这几个类不主动调用,直接启动
- 打印结果
1 | CateogryDemo[29670:414343] +[YZPerson load] |
这说明了。load方法,根本不需要我们自己调用,编译完成之后,就会调用。
疑问
但是有个疑问,因为,原来的类和分类中都写了load方法,为啥都调用呢?为什么不是只调用分类中的呢?
查看元类中的方法
利用runtime写个打印方法
1 | void printMethodNamesOfClass(Class cls) |
调用,因为是要查看类方法,所以需要打印元类中的方法
1 | - (void)viewDidLoad { |
结果为
1 | CateogryDemo[30112:420944] +[YZPerson load] |
小结
看得出来,有三个load方法,三个run方法
也进一步验证了,前面查看源码分析的结论:合并分类的时候,其方法列表等,不会覆盖掉原来类中的方法,是共存的。
源码分析
同上,先找到初始化方法
1 | void _objc_init(void) |
查看 load_images 方法,这个是加载镜像,模块的方法
1 | // 加载镜像,模块的方法 |
查看加载load的代码
1 | void call_load_methods(void) |
可以看出,是先调用类的load方法,再调用分类的load方法
继续跟代码
1 | static void call_class_loads(void) |
跟着上面注释中的关键代码,继续看源码
1 | typedef void(*load_method_t)(id, SEL); |
发现是一个指向函数地址的指针
小结
- 那么,答案就很清晰了。+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用。
有分类和子类的情况
当有分类,也有子类,并且都有load方法 的情况下
例如YZStudent是 YZPerson 的子类
YZStudent+test1 是 YZStudent的分类
调用结果为
1 | CateogryDemo[31904:444099] +[YZPerson load] |
源码分析
接着之前的源码分析,继续查看源码
1 | // 加载镜像,模块的方法 |
1 | void prepare_load_methods(const headerType *mhdr) |
1 | static void schedule_class_load(Class cls) |
可以看到,load方法,是递归调用,传入父类。依次加入数组中,那么调用的时候,先调用父类的load,再调用子类的load,而且和编译顺序无关。
总结
+load方法会在runtime加载类、分类时调用
每个类、分类的+load,在程序运行过程中只调用一次
load的调用顺序
先调用类的load
- 先编译的类,优先调用load
- 调用子类的load之前,会先调用父类的load
再调用分类的load
- 先编译的分类,优先调用load
+initialize方法
+initialize方法会在类第一次接收到消息时调用
调用顺序
先调用父类的+initialize,再调用子类的+initialize
(先初始化父类,再初始化子类,每个类只会初始化1次)
源码解读
runtime源码
objc4源码解读过程
1 | >1. objc-msg-arm64.s |
具体来看代码
1 |
|
继续查看 lookUpImpOrNil
1 | IMP lookUpImpOrNil(Class cls, SEL sel, id inst, |
lookUpImpOrForward 代码比较长,摘取关键代码如下
1 | IMP lookUpImpOrForward(Class cls, SEL sel, id inst, |
继续查看关键代码
1 | void _class_initialize(Class cls) |
这里可以看出,如果一个类,其父类没有初始化,就递归调用该方法进行初始化。最终调用callInitialize进行初始化
1 | void callInitialize(Class cls) |
最终,调用到objc_msgSend 方法,那我们继续看objc_msgSend源码,发现是汇编代码
截取部分如下:
1 | .data |
这也说明了,objc_msgSend性能高的原因,是因为直接操作汇编。
上述流程,用伪代码表示就是如下,其中
YZStudent 继承自 YZPerson
1 | if (YZStudent没有初始化) { |
注意点
+initialize和+load的很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点
- 如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
- 如果分类实现了+initialize,就覆盖类本身的+initialize调用
对比 +load 和 + initialize 的总结
load、initialize方法的区别什么?
调用方式
- load是根据函数地址直接调用
- initialize是通过objc_msgSend调用
调用时刻
- load是runtime加载类、分类的时候调用(只会调用1次)
- initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
load、initialize的调用顺序?
load
先调用类的load
- 先编译的类,优先调用load
- 调用子类的load之前,会先调用父类的load
再调用分类的load
- 先编译的分类,优先调用load
initialize
- 先初始化父类
- 再初始化子类(可能最终调用的是父类的initialize方法)
本文相关代码github地址 github
本文参考资料: