『底层探索』5 - 「类和方法」的归属问题
本文主要探索的是类的归属和方法的归属问题。
今天探索下类的归属和方法的归属。在 Objective-C 中,对于某个对象,
- 可以用
class
方法来获取这个对象所属的类 - 可以用
isKindOfClass
方法来判断是否是某类的子类 - 可以用
isMemberOfClass
方法来判断是否是某类的实例
对于某个类,
- 可以用
class_getInstanceMethod
来查找是否存在某个实例方法 - 可以用
class_getClassMethod
来查找是否存在某个类方法
接下来将会探索以上提到的这些方法的底层实现。
RDTeacher
首先声明一个继承于 NSObject
的 RDTeacher
类, 它:
- 实现了一个
sayStandUp
的类方法 - 实现了一个
sayByebye
的实例方法
完整代码如下。
1 | @interface RDTeacher : NSObject |
class
首先探索 class
方法,看看下面的 testClass
方法,你觉得 cls0 和 cls1 的地址会相同么?那么 cls2 和 cls3 呢?记住你此时的答案。
1 | void testClass() { |
看看打印的结果是啥?
1 | 0x100003550 -- 0x100003550 |
有没有出乎你的意外,为什么实例对象和类对象的 class 是一样的呢。在调用 class 的地方打个断点,运行到断点处。然后打开 Xcode
-> Debug
-> Debug workflow
-> Always Show Disassembly
后,编辑区的代码会变成汇编形式的。我们会发现调用 class
方法实际在底层调用的是 objc_opt_class
方法。
接下来在包含 objc 781
源码的 HelloWorld
工程中全局搜索 objc_opt_class
关键词,会找到如下的源码。可编译的 HelloWorld
工程在这里下载 objc-runtime。
1 | // Calls [obj class] |
可以看出,在 __OBJC2__ 中主要是通过 getIsa()
获取对象所属的 class
,然后根据所属 cls
是否是 meta class
返回不同的 class
。 如果是元类,则返回类对象本身,否则返回该对象所属的类。
为什么要这么设计呢?当你看到本文最后的 isa
走向图,你就会恍然大悟的,暂时先记着这个逻辑。
class_getInstanceMethod
下面代码块创建了一个 RDTeacher
类型的对象 t,然后得到对象 t 的类 cls 和元类 metaCls。我们分别在这两个 class 中查找 sayByebye
实例方法和查找 sayStandUp
对象方法,猜猜能找到么?
1 | void testGetMethod() { |
根据方法的功能定义分析,method0 能找到,method1 找不到。method2 能找到,method3 找不到。看看打印结果是否和我们的分析一致。
1 | getInstanceMethod --> 0x100003118 0x0 |
为什么 method3 能找到,也就是说,class_getClassMethod
不管传入的是类还是元类,都能查找到类方法。
这是为什么呢?看看 class_getInstanceMethod
源码是怎么实现的。
1 | Method class_getInstanceMethod(Class cls, SEL sel) |
方法首先是判断了 cls 和 sel 是否是 nil,非 nil 后,走了消息查找和转发流程。最后调用了 _class_getMethod
。看看这个方法是怎么实现的?
1 | static Method _class_getMethod(Class cls, SEL sel) |
这个方法接着调用了 getMethod_nolock
,继续探索。
1 | static method_t * |
这个方法是在所属类的 superclass
继承链上循环调用 getMethodNoSuper_nolock
方法查找 method。
该方法的实现如下,关于方法查找的细节流程,在后续的文章会有介绍,这里就不深入了。
1 | static method_t * |
class_getClassMethod
我们看看 class_getClassMethod
方法是如何实现的。细节往往藏在源码之中。
1 | Method class_getClassMethod(Class cls, SEL sel) |
class_getClassMethod
的实现比较巧妙,寻找类的类方法,相当于寻找元类的实例方法。因为对象所属的类是类,类对象所属的类是元类。再看看 getMeta()
方法做了啥?
1 | Class getMeta() { |
如果类对象所属类是元类返回本身,否则返回所属的类。 根据这个逻辑,对类对象和该对象所属的元类查找相同的类方法,底层调用逻辑是一样的,都是在元类中查找对象方法。 所以 method3 也能找到类方法的原因水落石出了。
isKindOfClass
isKindOfClass
用于判断一个对象的类是否是某个类的子类。看看下面的打印结果是否如你所想的一样。首先看看 isKindOfClass
底层实现。
1 | // Calls [obj isKindOfClass] |
该方法的核心逻辑是通过 getIsa
找到该对象所属的类,然后在该类的 superclass 继承链上寻找是否有类等于指定的类。
1 | void testKindOf() { |
以上的代码的打印结果是 1 0 1 1
, 为什么会是这个结果呢?请看下面的分析:
对于 res0,
[NSObject class]
得到的是NSObject 类
,类对象getIsa
得到的是NSObject 根元类
,根元类的superclass
是NSObject 类
,在根元类的superclass
继承链上可以找到NSObject 类
。 所以得到的结果是 1。对于 res1,
[RDTeacher class]
得到的是RDTeacher 类
,类对象getIsa
得到的是RDTeacher 根元类
,在RDTeacher 根元类
的superclass
继承链上找不到RDTeacher 类
。所以得到的结果是 0。对于 res3,
[NSObject alloc]
得到的是NSObject 对象
,对象getIsa
得到的是NSObject 类
,NSObject 类
的superclass
继承链上可以找到NSObject 类
。 所以得到的结果是 1。对于 res4,
[RDTeacher alloc]
得到的是RDTeacher 对象
,对象getIsa
得到的是RDTeacher 类
,在RDTeacher 类
的superclass
继承链上可以找到RDTeacher 类
。所以得到的结果是 1。
isMemberOfClass
isMemberOfClass
用于判断一个对象是否是某个类的实例。看看 isMemberOfClass
底层实现。
1 | + (BOOL)isMemberOfClass:(Class)cls { |
通过源码,我们可以得出该方法的核心逻辑:
- 如果是类对象,则判断类对象 isa 指向的类是否和指定类相同。
- 如果是对象,则判断对象所属的类是否和指定类相同。
看看下面的代码打印结果是否如你所想的一样?
1 | void testMemberOf() { |
以上的代码的打印结果是 0 0 1 1
, 为什么会是这个结果呢?请看下面的分析:
对于 res4,
[NSObject class]
得到的是NSObject 类
,类对象ISA()
得到的是NSObject 根元类
,NSObject 根元类
和NSObject 类
是不同的 ,所以得到的结果是 0。对于 res5,
[RDTeacher class]
得到的是RDTeacher 类
,类对象ISA()
得到的是RDTeacher 根元类
,RDTeacher 根元类
和RDTeacher 类
是不相同的。所以得到的结果是 0。对于 res6,
[NSObject alloc]
得到的是NSObject 对象
,对象ISA()
得到的是NSObject 类
,NSObject 类
和NSObject 类
是相同的。 所以得到的结果是 1。对于 res7,
[RDTeacher alloc]
得到的是RDTeacher 对象
,对象ISA()
得到的是RDTeacher 类
,在RDTeacher 类
和RDTeacher 类
是相同的。所以得到的结果是 1。
最后附上 Apple 经典的 isa 走位图,如果你能把这张图了然于胸,上面的这些对你来说就是 small cake。
总结
isKindOfClass
是在对象所属的类的superclass
继承链上寻找是否有 class 等于给定的 class。这里要注意的是,对象和类对象调用class
方法得到的都是 「类对象」。isMemberOfClass
是判断对象所属的类是否等于给定的 class。class_getClassMethod
方法,是通过调用class_getInstanceMethod
方法实现的,只不过传的参数是类对象所属的元类而已。
后记
我是穆哥,卖码维生的一朵浪花。我们下回见。
文章作者:muhlenXi
原始链接:https://muhlenxi.com/2020/09/15/077-kind-member/
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!