Objective-C的+initialize方法调用原理分析
2019-05-21 本文已影响31人
RUNNING_NIUER
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(
来搜索一下,结果如下图
可以看到能在源码里面找到的相关文件都是一堆.s文件,也就是汇编文件,说明源码提供了该函数的汇编实现,这是一种半开源形式,想要读懂,就需要汇编基础。令人悲伤的是,此时此刻码字的我,还不会汇编,自然也就看不懂。
不过发现注释里面有一句代码,是跟方法查询相关的函数,
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的+initialize实现
接着上面的场景三,我们对CLStudent和CLTeacher的进行微调,不实现他们的+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
方法而被调用。