天天看点

Objective-C的+initialize方法调用原理分析

Objective-C的+load方法调用原理分析

Objective-C之Category的底层实现原理

Objective-C为我们提供了两种方法去运行对类进行相关设置的代码。

  • +load

    :该方法会在很早阶段(同时也是比较危险的阶段,可能导致崩溃)被调用,一旦某个类被Runtime加载,该类的

    +load

    方法就会被调用。我们可以在这个方法里面写一些必须要在程序运行非常早期阶段就需要运行的代码。
  • +initialize

    :该方法可以比较安全的处理大部分情况下的设置任务代码,因为会在一个更加安全的环境下被调用。你几乎可以在这个方法里面做任何事情,除非,

    你的代码

    需要等到外部实体向这个类发消息之后,才能运行,那么将

    你的代码

    放在

    +initialize

    方法里面将是不合适的。
关于+initialize方法的一些结论
  • +initialize

    方法会在类第一次接收到消息的时候调用
  • +initialize

    方法是通过

    objc_msgSend()

    进行调用的
分析
Objective-C的+initialize方法调用原理分析

+initialize

既然是在类对象第一次接受消息的时候调用,我们知道接受消息整个逻辑的底层其实就是通过

objc_msgSend(Class cls, SEL sel)

函数开始的,而该函数主要思路就是首先通过

isa

先进行方法的查找,找到后就进行方法调用。所以系统对

+initialize

的调用,就可能发生在上述的两个步骤之中。

撸一波源码

那么我们来看看这个函数的源码,通过关键字

objc_msgSend(

来搜索一下,结果如下图

Objective-C的+initialize方法调用原理分析
Objective-C的+initialize方法调用原理分析

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

Objective-C的+initialize方法调用原理分析

随便点开一个,比如说arm64.s的文件,看到如此晦涩的汇编代码,我真的有心无力,只能默默鼓励自己:学海无涯,前路漫漫~~

不过发现注释里面有一句代码,是跟方法查询相关的函数,

objc_msgLookup(id self, SEL _cmd, ...)

,那么继续查看一下,万一是看得懂的C函数呢,走起

Objective-C的+initialize方法调用原理分析

结果还是令人失望,除了一个系统的函数定义,没有找到相关的实现。

看来这个方向是暂时走不通了。还好,我从大佬MJ老师那里,了解到一个跟

objc_msgLookup

等价的方法,它就是

Method class_getInstanceMethod(Class cls, SEL sel)

。进入该方法查看一下

Objective-C的+initialize方法调用原理分析

我们在

objc-runtime-new.mm

文件下(很明显

objc-runtime-old.mm

应该是过时的源码)看到该函数的实现里面,有一个

lookUpImpOrNil

函数,这个便是具体的方法查找函数,继续进入其中

Objective-C的+initialize方法调用原理分析

里面又包了一层,话不多说,继续进入函数

lookUpImpOrForward

Objective-C的+initialize方法调用原理分析

终于我们发现了想要的东西,该函数里面,可以开到在一开始,就有一段与类对象初始化相关的逻辑,如上图红框,我把它转成伪代码的形式便于理解

if (需要初始化  &&  class还没进行过初始化) {
        对class进行初始化
    }
           
️️️注意,上面这段为代码逻辑,是发生在方法查找过程的,也就是说,类对象每次接收到消息,进行方法查找的时候,都会进入这段逻辑,很明显,该逻辑中,if判断条件就确保了,对于类对象的初始化操作只会进行一次,并且发生在类对象第一次接收到消息的时候。

那么看看对类对象进行初始化的具体过程,也就是

_class_initialize

函数,进入

Objective-C的+initialize方法调用原理分析

针对我们研究的问题,我们找到关键部分代码,该函数里面,先判断了父类是否被initialized,如果没有的话,递归调用本函数对父类进行处理,完毕之后,在通过

callInitialize()

+initialize

进行实际调用。

Objective-C的+initialize方法调用原理分析

callInitialize()

的实现,也证实了,系统确实是通过消息机制

objc_msgSend()

来调用

+initialize

方法的。好了,源代码分析结束。
上机调试
场景一
Objective-C的+initialize方法调用原理分析
Objective-C的+initialize方法调用原理分析
Objective-C的+initialize方法调用原理分析
Objective-C的+initialize方法调用原理分析
小结(一):该场景证明了,

+initialize

方法的调用发生在类对象第一次接受消息的时候。
场景二
Objective-C的+initialize方法调用原理分析
Objective-C的+initialize方法调用原理分析
Objective-C的+initialize方法调用原理分析
Objective-C的+initialize方法调用原理分析
Objective-C的+initialize方法调用原理分析
小结(二):该场景证明了,系统对

+initialize

方法的调用是通过消息机制,也就是

objc_msgSend

函数来发起的,根据我的Objective-C之Category的底层实现原理一文对

Category

的“方法覆盖”现象的研究,也是支持该场景下的最后日志打印结果:打印的是–

CLPerson+Cate02

+initialize

方法–。
场景三
Objective-C的+initialize方法调用原理分析
Objective-C的+initialize方法调用原理分析
Objective-C的+initialize方法调用原理分析
Objective-C的+initialize方法调用原理分析
Objective-C的+initialize方法调用原理分析
Objective-C的+initialize方法调用原理分析
小结(三):该场景证明了我们从源码中发现的逻辑:在+initialize方法都实现了的前提下,系统对一个类对象调用

+initialize

方法的之前,会先调用其父类的

+initialize

方法(️要求父类的

+initialize

方法必须从来没有被调用过)

场景四

接着上面的场景三,我们对

CLStudent

CLTeacher

的进行微调,不实现他们的

+initialize

Objective-C的+initialize方法调用原理分析
Objective-C的+initialize方法调用原理分析
Objective-C的+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

    方法而被调用。
好了,关于initialize的调用原理分析,就到这里结束了,各位看官有空常来,慢走不送~~

Objective-C的+load方法调用原理分析

Objective-C之Category的底层实现原理