iOS底层之isKindOfClass

前言

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);
}
复制代码

这里直接上代码,这里分别列举了几种情况的调动。

  • 基类和自定义类分别调用isKindOfClassisMemberOfClass类方法
  • 基类和自定义类的实例对象分别调用isKindOfClassisMemberOfClass实例方法
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;
}
复制代码

image.png
NSObject调用isKindOfClass和isMemberOfClass的类方法。
image.png
DDAnimal调用isKindOfClass和isMemberOfClass的类方法。
image.png
NSObject的实例对象调用isKindOfClass和isMemberOfClass的实例方法。
image.png
DDAnimal的实例对象调用isKindOfClass和isMemberOfClass的实例方法。

分析的结果和打印的结果一样。但是但是但是这样分析真的是对的吗?
通过断点调试,发现只有isMemberOfClass进入了断点,但是isKindOfClass却并没有进入断点,这是为何呢?

进入断点调试,打开反汇编调试,打开方式如下图。

image.png

image.png

调试界面可以看到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__环境下,则会进入它们相对应的方法。

总结

这里实际上就是有一个符号表的坑,你以为它是调用的这个方法,但是底层给你改了,诶,就是玩儿。所以在探究底层的时候还是要多打断点看,多看反汇编调试结果。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享