preface

IsKindOfClass and isMemberOfClass seem to be very simple methods, mainly test your understanding of the isa trend and inheritance relationship, let’s look at the underlying implementation, see if there is any pit here.

code

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);
}
Copy the code

Here directly on the code, here respectively enumerated several cases of mobilization.

  • Base and custom classes are called separatelyisKindOfClassandisMemberOfClasstheClass method;
  • Base and custom class instance objects are called separatelyisKindOfClassandisMemberOfClasstheInstance methods;
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
Copy the code

Print the result as above. Using the command+ click method, we find the source code, according to the source code to verify whether it is correct.

+ (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;
}
Copy the code

NSObject calls the class methods of isKindOfClass and isMemberOfClass.DDAnimal calls the isKindOfClass and isMemberOfClass class methods.The instance object of NSObject calls the instance methods of isKindOfClass and isMemberOfClass.The DDAnimal instance object calls the isKindOfClass and isMemberOfClass instance methods.

The results of analysis are the same as those of printing. But but but is that really the right analysis? Breakpoint debugging shows that only isMemberOfClass enters the breakpoint, but isKindOfClass does not enter the breakpoint.

Enter breakpoint debugging and open disassembly debugging as shown in the following figure.

IsMemberOfClass is the original method, but isKindOfClass is the objc_opt_isKindOfClass.

objc_opt_isKindOfClass

Source code global search objc_opt_isKindOfClass, find a way to implement.

// 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);
}
Copy the code

In this section of code, you can see that the logic is the same as the code you saw before, so the results look the same. The only difference is the way in which an ISA is obtained.

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;
}
Copy the code

Only class methods use this function.

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
}
Copy the code

In fact, both class methods and instance methods use this function in __OBJC2__, and if not in __OBJC2__, their corresponding methods are entered.

conclusion

This is actually a pit with a symbol table, and you think it’s calling this method, but the bottom layer has changed it for you, oops, just for fun. So in exploring the bottom of the time or to interrupt the point to see, see disassembly debugging results.