类的方法归属 & isKindOf和isMemberOf解析

上一篇我们通过研究类的结构,解析了类的属性,成员变量,实例方法和类方法所在的位置。本篇我会从另一个角度探究类的类方法的储存位置以及isKindOf和isMemberOf的解析。

类方法的归属问题

上一节我抄了近路,直接使用著名的一句话:”类方法是元类的实例方法“找到了类方法+ (void)classMethod;。今天我会使用苹果api来找到类方法的存储位置。

前期准备工作

首先介绍一下会用到的api:

class_getName()           // 获取char类型类名称
objc_getMetaClass()    // 获取类的元类
class_getInstanceMethod()    // 判断类中是否有传入的实例方法并返回方法地址
class_getClassMethod()    // 判断类中是否有传入的类方法并返回方法地址
class_getMethodImplementation()     // 判断类中是否有传入方法的实现Imp,并返回Imp地址

工程还是用我们上一篇的工程,创建一个实例方法,一个类方法,并实现一下:


image.png

1.通过打印方法列表验证结论
通过打印类的方法名来验证一下我们上一篇的结论:类方法不在当前类的方法列表中,而是在元类的实例方法列表中:

void fcObjc_copyMethodList(Class pClass){
    unsigned int count = 0;
    Method *methods = class_copyMethodList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Method const method = methods[I];
        //获取方法名
        NSString *key = NSStringFromSelector(method_getName(method));
        
        FCLog(@"Method, name: %@", key);
    }
    free(methods);
}

调用方法:

// 当前类FCPerson
        FCPerson *person = [FCPerson alloc];
        Class pClass = object_getClass(person);
        
        // FCPerson的元类
        const char *className = class_getName(pClass);
        Class metaClass = objc_getMetaClass(className);
        
        // FCPerson的方法列表
        fcObjc_copyMethodList(pClass);
        // FCPerson元类的方法列表
        NSLog(@"*************");
        fcObjc_copyMethodList(metaClass);

输出结果:


image.png

验证成功:类方法不存在类中,而是存在元类中,类方法是元类的实例方法

2.通过class_getInstanceMethod()输出类/元类中是否含有对应实例方法来验证:

void fcInstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    // 类/元类中是否含有实例方法instanceMethod
    Method method1 = class_getInstanceMethod(pClass, @selector(instanceMethod));
    Method method2 = class_getInstanceMethod(metaClass, @selector(instanceMethod));
    // 类/元类中是否含有类方法classMethod
    Method method3 = class_getInstanceMethod(pClass, @selector(classMethod));
    Method method4 = class_getInstanceMethod(metaClass, @selector(classMethod));
    
    FCLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

调用&输出:


image.png

如图:FCPerson中是否含有instanceMethod :有
FCPerson中是否含有classMethod :没有
FCPerson元类中是否含有instanceMethod :没有
FCPerson元类中是否含有classMethod :有,验证成功

3.通过class_getClassMethod()输出类/元类中是否含有对应的类方法来验证:

void fcClassMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(instanceMethod));
    Method method2 = class_getClassMethod(metaClass, @selector(instanceMethod));

    Method method3 = class_getClassMethod(pClass, @selector(classMethod));
    Method method4 = class_getClassMethod(metaClass, @selector(classMethod));
    
    FCLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

调用&输出:


image.png

如图:FCPerson中是否含有类方法instanceMethod :没有
FCPerson的元类中是否含有类方法instanceMethod :没有
FCPerson中是否含有类方法classMethod :有
FCPerson的元类中是否含有类方法classMethod :有

为什么FCPerson的元类中也含有类方法classMethod??
查看class_getClassMethod源码:

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    // 获取类方法实际上获取的是元类的实例方法
    return class_getInstanceMethod(cls->getMeta(), sel);
}
Class getMeta() {
        // 获取元类时会先判断传入的是否是元类,如果是元类则返回this(自己)
        if (isMetaClassMaybeUnrealized()) return (Class)this;
        else return this->ISA();
    }

class_getClassMethod和其中getMeta()的实现可以得知:获取类方法实际上获取的是元类的实例方法,而在getMeta()获取元类时会先判断传入的是否是元类,如果是元类则返回this(自己),因此输出结果为:FCPerson的元类中是否含有类方法classMethod :有

4.分别查看实例方法和类方法在类和元类中是否含有方法实现Imp:

void fcIMP_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    IMP imp1 = class_getMethodImplementation(pClass, @selector(instanceMethod));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(instanceMethod));// 0
    // sel -> imp 方法的查找流程 imp_farw
    IMP imp3 = class_getMethodImplementation(pClass, @selector(classMethod)); // 0
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(classMethod));

    NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
    NSLog(@"%s",__func__);
}

调用&输出:


image.png

如图:FCPerson中是否含有实例方法instanceMethod的实现:有
FCPerson中是否含有类方法classMethod的实现:有???
FCPerson元类中是否含有实例方法instanceMethod的实现:有???
FCPerson元类中是否含有类方法classMethod的实现:有

明明FCPerson中不应该包含类方法classMethod的实现,FCPerson元类中不应该有实例方法instanceMethod的实现,但为什么结果是有的呢?仔细看下日志发现两条返回Imp地址为同一个:0x7fff6767d580。Why?

当对一个对象发送一条消息,找不到对应实现的时候会发生什么呢?没错,就是我们熟悉的消息转发流程啦,进入class_getMethodImplementation同样可以看到,当找不到Imp的时候会返回_objc_msgForward:

IMP class_getMethodImplementation(Class cls, SEL sel) {
    IMP imp;

    if (!cls  ||  !sel) return nil;

    lockdebug_assert_no_locks_locked_except({ &loadMethodLock });

    imp = lookUpImpOrNilTryCache(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);

    // Translate forwarding function to C-callable external version
    if (!imp) {
        return _objc_msgForward;
    }

    return imp;
}

因此0x7fff6767d580其实就是_objc_msgForward,我们之前的结论还是没问题滴。

至此,我们通过多个方式验证了我们的结论:类方法不存在类中,而是存在该类的元类中,类方法是元类的实例方法。

补充知识:为什么要有元类?我用很简单的例子来解释说明一下,看代码:

- (void)saySomething;
+ (void)saySomething;

这样写有没有问题?没问题对吧,虽然方法名相同,但一个是实例方法,一个是类方法。但这只是OC层面的写法,如果还原到底层的C/C++层面,他们其实就都是函数,底层是没有实例方法和类方法的概念的,那么编译器如何区分这两个方法呢?苹果也是发现了有这个坑点,所以生成了类的元类,把类方法以实例方法的形式存在元类中,解决了这个问题。

isKindOf和isMemberOf解析###

isKindOf和isMemberOf这对方法经常出现在面试题中,那么他们具体是做什么用的,有什么含义,有什么区别?我们接下来做详细解析,首先看经典题目:

FCPerson *person = [FCPerson alloc];
        NSObject *obj = [NSObject alloc];
        
        BOOL re1 = [[NSObject class] isKindOfClass:[NSObject class]];
        BOOL re2 = [obj isKindOfClass:[NSObject class]];
        BOOL re3 = [[FCPerson class] isKindOfClass:[FCPerson class]];
        BOOL re4 = [person isKindOfClass:[FCPerson class]];
        NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
        
        BOOL re5 = [[NSObject class] isMemberOfClass:[NSObject class]];
        BOOL re6 = [obj isMemberOfClass:[NSObject class]];
        BOOL re7 = [[FCPerson class] isMemberOfClass:[FCPerson class]];
        BOOL re8 = [person isMemberOfClass:[FCPerson class]];
        NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);

结果如何先不说,大家可以先自己思考,我们先依次来看源码,先看isKindOf:

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

isKindOfClass()分为类方法和实例方法,两个方法内部都是循环遍历。
类方法的含义为:第一次循环先找出自己的元类,然后去跟传入类比较,如果不相等再去找元类的父类,以此类推。
实例方法含义为:第一次循环先获取自己的当前类,然后去跟传入类比较,如果不相等再去找父类,以此类推。
很容易看出类方法和实例方法的区别在于,类方法使用元类链来比较,实例方法使用的是对象本身的类链,可以参考isa的走位图,看元类和类两条链的继承关系

isa流程图.png

套用到题目中:
re1 = [[NSObject class] isKindOfClass:[NSObject class]]:使用类方法,NSObject的元类为根元类,根元类!=NSObject,继续循环,根元类的父类为根类,根类==NSObject,结果为1;

re2 = [obj isKindOfClass:[NSObject class]]:实例方法,objc的类为NSObject,NSObject==NSObject,结果1;

re3 = [[FCPerson class] isKindOfClass:[FCPerson class]]:类方法,FCPerson的元类!= FCPerson,继续向父类查找,很明显不会再等于FCPerson,结果0;

re4 = [person isKindOfClass:[FCPerson class]]:实例方法,person的类为FCPerson,第一次循环返回1;

看完isKindOf再来看isMemberOf,源码:

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

同样是类方法和实例方法,类方法返回元类是否等于传入类,实例方法返回当前对象的类是否等于传入类。再来套用题目:
re5 = [[NSObject class] isMemberOfClass:[NSObject class]]:类方法,NSObject的元类为根元类,不等于NSObject,返回0;

re6 = [obj isMemberOfClass:[NSObject class]]:实例方法,obj的类为NSObject,返回1;

re7 = [[FCPerson class] isMemberOfClass:[FCPerson class]]:类方法,FCPerson的元类不等于FCPerson,返回0;

re8 = [person isMemberOfClass:[FCPerson class]]:实例方法,person的类为FCPerson,返回1;

知识补充#:以上对isMemberOfClass和isKindOfClass的分析只针对于模拟器,实际上用真机调用的时候,系统会将isKindOfClass方法重定向为objc_opt_isKindOfClass ,源码缩略后如下:

objc_opt_isKindOfClass(id obj, Class otherClass)
{
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}

isKindOfClass方法相比较,首先获取cls的方法为Class cls = obj->getIsa();:

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;
}

getIsa()方法中做了很多位置运算,但是只看最终的cls可以得知要么cls = objc_tag_classes[slot];,要么cls = objc_tag_ext_classes[slot];

#define objc_tag_classes objc_debug_taggedpointer_classes
#define objc_tag_ext_classes objc_debug_taggedpointer_ext_classes
extern "C" { 
    extern Class objc_debug_taggedpointer_classes[_OBJC_TAG_SLOT_COUNT];
    extern Class objc_debug_taggedpointer_ext_classes[_OBJC_TAG_EXT_SLOT_COUNT];
}

以我目前的水平其实并不能看太懂,但是只通过ext来判断,猜测objc_tag_classes []获取的是当前类,objc_tag_ext_classes []获取的是当前类的元类。
按照这样的猜测继续看objc_opt_isKindOfClass源码,其实可以看出当传入的obj是类的时候,cls就是类的元类;传入obj为对象时,cls就是obj的类。然后经过了一个判断if (fastpath(!cls->hasCustomCore())),如果符合条件则进入到跟之前相同的循环判断,得出相同的结果;如果不符合条件则一律返回No。

对比objc_opt_isKindOfClassisKindOfClass两个方法,为什么系统要重定向isKindOfClass?看起来关键就在于判断条件if (fastpath(!cls->hasCustomCore())),贴出hasCustomCore的代码:

    #if FAST_CACHE_HAS_DEFAULT_CORE
    bool hasCustomCore() const {
        return !cache.getBit(FAST_CACHE_HAS_DEFAULT_CORE);
    }

    #if __LP64__
    bool getBit(uint16_t flags) const {
        return _flags & flags;
    }

是跟缓存相关的,目前我还看不出什么情况下会不符合条件,如果以后知道了会继续补充,有详细了解的大佬也欢迎下方留言哦,我们下节见~

你可能感兴趣的:(类的方法归属 & isKindOf和isMemberOf解析)