前言
isKindOfClass和isMemberOfClass,看上去是很简单的方法,主要是考验自己对isa走向和继承关系的理解程度,我们来看看底层到底是怎么实现的,看看这里有没有什么坑。
代码
void lgKindofDemo(void){
BOOL re1 = [[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re3 = [[DDAnimal class] isKindOfClass:[DDAnimal class]];
BOOL re4 = [[DDAnimal class] isMemberOfClass:[DDAnimal class]];
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
BOOL re5 = [[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL re6 = [[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL re7 = [[DDAnimal alloc] isKindOfClass:[DDAnimal class]];
BOOL re8 = [[DDAnimal alloc] isMemberOfClass:[DDAnimal class]];
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}
复制代码
这里直接上代码,这里分别列举了几种情况的调动。
- 基类和自定义类分别调用
isKindOfClass
和isMemberOfClass
的类方法
; - 基类和自定义类的实例对象分别调用
isKindOfClass
和isMemberOfClass
的实例方法
;
2021-06-25 15:19:40.426546+0800 DDObjcBuild[66155:3395282]
re1 :1
re2 :0
re3 :0
re4 :0
2021-06-25 15:19:40.426636+0800 DDObjcBuild[66155:3395282]
re5 :1
re6 :1
re7 :1
re8 :1
复制代码
打印结果如上。
通过command+点击方法,我们找到源码,根据源码来一一印证是否正确。
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
复制代码
NSObject调用isKindOfClass和isMemberOfClass的类方法。
DDAnimal调用isKindOfClass和isMemberOfClass的类方法。
NSObject的实例对象调用isKindOfClass和isMemberOfClass的实例方法。
DDAnimal的实例对象调用isKindOfClass和isMemberOfClass的实例方法。
分析的结果和打印的结果一样。但是但是但是这样分析真的是对的吗?
通过断点调试,发现只有isMemberOfClass进入了断点,但是isKindOfClass却并没有进入断点,这是为何呢?
进入断点调试,打开反汇编调试,打开方式如下图。
调试界面可以看到isMemberOfClass是用的原本的方法,但是isKindOfClass却是调用的objc_opt_isKindOfClass
,终于找到罪魁祸首在这里。
objc_opt_isKindOfClass
源码中全局搜索objc_opt_isKindOfClass
,找到方法实现。
// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
复制代码
这部分代码可以看到逻辑和之前看到的代码是一样的,所以结果看上去才会一样。唯一不同的是获取isa的方式不同。
inline Class
objc_object::getIsa()
{
if (fastpath(!isTaggedPointer())) return ISA();
extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
uintptr_t slot, ptr = (uintptr_t)this;
Class cls;
slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
cls = objc_tag_classes[slot];
if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
cls = objc_tag_ext_classes[slot];
}
return cls;
}
复制代码
只有类方法使用这个函数。
inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
#if SUPPORT_INDEXED_ISA
return cls;
#else
uintptr_t clsbits = bits;
# if __has_feature(ptrauth_calls)
# if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
// Most callers aren't security critical, so skip the
// authentication unless they ask for it. Message sending and
// cache filling are protected by the auth code in msgSend.
if (authenticated) {
// Mask off all bits besides the class pointer and signature.
clsbits &= ISA_MASK;
if (clsbits == 0)
return Nil;
clsbits = (uintptr_t)ptrauth_auth_data((void *)clsbits, ISA_SIGNING_KEY, ptrauth_blend_discriminator(this, ISA_SIGNING_DISCRIMINATOR));
} else {
// If not authenticating, strip using the precomputed class mask.
clsbits &= objc_debug_isa_class_mask;
}
# else
// If not authenticating, strip using the precomputed class mask.
clsbits &= objc_debug_isa_class_mask;
# endif
# else
clsbits &= ISA_MASK;
# endif
return (Class)clsbits;
#endif
}
复制代码
实际上类方法和实例方法在__OBJC2__
环境下都是使用的这个函数,如果不是__OBJC2__
环境下,则会进入它们相对应的方法。
总结
这里实际上就是有一个符号表的坑,你以为它是调用的这个方法,但是底层给你改了,诶,就是玩儿。所以在探究底层的时候还是要多打断点看,多看反汇编调试结果。