Objective-C的+load方法调用原理分析
Objective-C之Category的底层实现原理
Objective-C为我们提供了两种方法去运行对类进行相关设置的代码。
-
+load:该方法会在很早阶段(同时也是比较危险的阶段,可能导致崩溃)被调用,一旦某个类被Runtime加载,该类的+load方法就会被调用。我们可以在这个方法里面写一些必须要在程序运行非常早期阶段就需要运行的代码。 -
+initialize:该方法可以比较安全的处理大部分情况下的设置任务代码,因为会在一个更加安全的环境下被调用。你几乎可以在这个方法里面做任何事情,除非,你的代码需要等到外部实体向这个类发消息之后,才能运行,那么将你的代码放在+initialize方法里面将是不合适的。
关于+initialize方法的一些结论
-
+initialize方法会在类第一次接收到消息的时候调用 -
+initialize方法是通过objc_msgSend()进行调用的
分析

+initialize方法的调用可能的发生的地方
+initialize既然是在类对象第一次接受消息的时候调用,我们知道接受消息整个逻辑的底层其实就是通过objc_msgSend(Class cls, SEL sel)函数开始的,而该函数主要思路就是首先通过isa先进行方法的查找,找到后就进行方法调用。所以系统对+initialize的调用,就可能发生在上述的两个步骤之中。
撸一波源码
那么我们来看看这个函数的源码,通过关键字objc_msgSend(来搜索一下,结果如下图

objc_msgSend的汇编实现

可以看到能在源码里面找到的相关文件都是一堆.s文件,也就是汇编文件,说明源码提供了该函数的汇编实现,这是一种半开源形式,想要读懂,就需要汇编基础。令人悲伤的是,此时此刻码字的我,还不会汇编,自然也就看不懂。

image.png
不过发现注释里面有一句代码,是跟方法查询相关的函数,
objc_msgLookup(id self, SEL _cmd, ...),那么继续查看一下,万一是看得懂的C函数呢,走起
看来这个方向是暂时走不通了。还好,我从大佬MJ老师那里,了解到一个跟
objc_msgLookup等价的方法,它就是Method class_getInstanceMethod(Class cls, SEL sel)。进入该方法查看一下
objc-runtime-new.mm文件下(很明显objc-runtime-old.mm应该是过时的源码)看到该函数的实现里面,有一个lookUpImpOrNil函数,这个便是具体的方法查找函数,继续进入其中
lookUpImpOrForward
if (需要初始化 && class还没进行过初始化) {
对class进行初始化
}
️️️注意,上面这段为代码逻辑,是发生在方法查找过程的,也就是说,类对象每次接收到消息,进行方法查找的时候,都会进入这段逻辑,很明显,该逻辑中,if判断条件就确保了,对于类对象的初始化操作只会进行一次,并且发生在类对象第一次接收到消息的时候。
那么看看对类对象进行初始化的具体过程,也就是_class_initialize函数,进入

针对我们研究的问题,我们找到关键部分代码,该函数里面,先判断了父类是否被initialized,如果没有的话,递归调用本函数对父类进行处理,完毕之后,在通过
callInitialize()对+initialize进行实际调用。
而callInitialize()的实现,也证实了,系统确实是通过消息机制objc_msgSend()来调用+initialize方法的。好了,源代码分析结束。
上机调试
场景一
CLPerson的+initialize方法
小结(一):该场景证明了,
+initialize方法的调用发生在类对象第一次接受消息的时候。
场景二
分类的编译顺序
CLPerson+Cate01的+initialize方法
CLPerson+Cate02的+initialize方法
小结(二):该场景证明了,系统对
+initialize方法的调用是通过消息机制,也就是objc_msgSend函数来发起的,根据我的Objective-C之Category的底层实现原理一文对Category的“方法覆盖”现象的研究,也是支持该场景下的最后日志打印结果:打印的是--CLPerson+Cate02的+initialize方法--。
场景三
CLStudent继承自CLPerson
CLStudent的+initialize方法
CLTeacher继承自CLPerson
CLTeacher的+initialize方法
小结(三):该场景证明了我们从源码中发现的逻辑:在+initialize方法都实现了的前提下,系统对一个类对象调用
+initialize方法的之前,会先调用其父类的+initialize方法(️要求父类的+initialize方法必须从来没有被调用过)
场景四
接着上面的场景三,我们对CLStudent和CLTeacher的进行微调,不实现他们的+initialize
注销CLStudent的+initialize实现
注销CLTeacher的+initialize实现
小结(四):从结果看,
CLPerson的+initialize方法被调用了三次。
- 第(1)次调用,是CLStudent首次接受消息时,系统对父类CLPerson进行的
+initialize调用,也就是objc_msgSend([CLPerson class] ,@selector(initialize))- 第(2)次调用,是CLStudent首次接受消息时,系统对CLStudent进行的
+initialize调用,也就是objc_msgSend([CLStudent class],@selector(initialize)),因为CLStudent没有实现自己+initialize方法,所以根据消息机制的原理,调用了父类CLPerson的+inilialize方法。- 第(3)次调用,是CLTeacher首次接受消息时,系统对CLTeacher进行的
+initialize调用,也就是objc_msgSend([CLTeacher class],@selector(initialize)),因为CLTeacher没有实现自己+initialize方法,所以根据消息机制的原理,调用了父类CLPerson的+inilialize方法。
总结
-
+initialize方法会在类对象 第一次 接收到消息的时候调用 - 调用顺序:调用某个类的
+initialize之前,会先调用其父类的+initialize(前提是父类的+initialize从来没有被调用过) - 由于
+initialize的调用,是通过消息机制,也就是objc_msgSend(),因此如果子类的+initialize没有实现,就会去调用父类的+initialize - 基于同样的原因,如果分类实现的
+initialize,那么就会“覆盖”类对象本身的+initialize方法而被调用。


















