起
我们先来看一下下面的一个面试题:
|
|
请问输出分别是多少呢?大家可以自己试一试,答案分别是:
|
|
承
我们直接看源码进行分析:
|
|
总结一下上面的判断逻辑大概是这样:
isKindOfClass会使用继承链比较,实例方法使用类对象的继承链,类方法使用元类对象的继承链。
isMemberOfClass直接比较,实例方法使用类对象,类方法使用元类对象。
看到这里,不得不再次祭出下面这张图了:
我们来瞧瞧元类对象的继承链,根元类的superclass指针指向的是根类,这也就是
|
|
这里为什么返回1的原因。
转
有了以上结论,我们再来Debug一下runtime的代码,看一下这几个函数内部的运行逻辑是否和我们上面的结论一致。
isMemberOfClass lldb分析
先来看看类方法:
|
|
这里就很清晰,类对象只有一个,因为他们的首地址和内部变量都相同 ,根元类对象也只有一个,但是和类对象首地址不同,ISA指针相同,这又证明了上图的内容,即根源类的ISA指向自己。根元类和类对象不相等,返回0。
|
|
因为是自定义的类,所以会有一个元类对象,元类对象isa指向上面的根元类,元类和类对象不相等,返回0,没有问题。
然后我们来看看实例方法,实例对象的内存分析和类对象有一点区别,请注意!!!
|
|
在分析前,我们先回到源码部分
|
|
这里的
|
|
怎么理解呢?
实际上分两种情况:
- 当self是实例对象时,class是实例方法,返回isa指针,也就是类对象
- 当self是类或者元类对象时,class是类方法,返回自己。
所以上面我需要拿到self的isa指向的内存,但是self的isa不能直接打印内存,因为nonpointer_isa对isa进行了优化,存储很多其他的信息,我们需要先使用面具过滤掉其他不需要的信息,才能拿到isa中存储的类对象地址。从lldb打印情况可以看到,self实例对象的isa指向的就是类对象,所以这里返回1.
re8的分析过程和re6相似,就不再重复劳动了。
特殊的isKindOfClass
为什么要加上【特殊】这个前缀呢,原因很简单,isKindOfClass里面的断点根本没有进去,可见runtime内部对这个方法的调用可能走的就不是我们上面的实现,这种情况我们之前在alloc的分析过程中也遇到过,所以不要慌。经过在源码中一番搜索,我们最终找到下面这个函数:
|
|
内部的判断逻辑和我们上面看到的源码一样,所有我们可以在这个函数中进行debug了,不过这个debug过程已经不是重点了,重点是为什么会走到这个函数里面,上面的函数又是什么时候调用,因为有了之前的经验,我们直接去llvm中看能否寻找的一些答案。
果不其然,我们在llvm的源码中发现了这个:
|
|
在注释中,我们发现了【加速派发】,【不常重写】等字样,这里我们大胆猜测就是llvm在底层为了优化方法的调用速度,对下面列表中的方法做了优化,会优先进到这些优化方法中去,如果我们重写isKindOfClass等方法,可能才会进入之前我们看到的函数中去。
ok 来尝试一下我们的猜测,很简单,我们只需要重写一下:
|
|
果然,在我们重写之后,之前的断点就生效了,完美!!!
合
源码的探索过程还是比较有趣的,不过如果你找不到正确的方法的话,可能跟到一个方法后就很难再深入进入,导致直接放弃,打击学习的积极性,所以还是要多看多学,不断积累才行啊。