isKindOfClass 和 isMemberOfClass 分析

365 阅读3分钟

举例子🌰

首先看一下如下代码的执行结果

BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
        BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //
        BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];       //
        BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];     //
        NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);

        BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       //
        BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     //
        BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];       //
        BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];     //
        NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);

执行结果如下:

isKindOfClass源码分析

Apple open source下载781源码,并配置成可编译的源码

在可编译的源码中找到isKindOfClass的实现,我们发现有两个:

类方法

+ (BOOL)isKindOfClass:(Class)cls {
    // 类 vs 元类
    // 根元类 vs NSObject
    // NSObject vs NSObject
    // LGPerson vs 元类 (根元类) (NSObject)
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

此函数是通过selfisa打到当前类的元类,判断元类是存在,如果存在就比较元类和当前是否相同,相同返回YES,不同,再打到元类的父类,再进行比较。此函数用来判断元类是否相同。

实例方法

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

此函数是通过[self class]拿到当前对象的类和传进来的类cls进行比较,如果相同,返回YES,如果不同,到当前对象的类的父类,再比较。此函数用来判断对象是否属于某个类或者其子类.

isMemberOfClass源码分析

同样的,有类方法和实例方法

类方法

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

此函数用于判断元类是否相同

实例方法

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

此函数用来判断对象是否属于某个类

那么当类或者对象在调用isKindOfClassisMemberOfClass时,真的会来到上面介绍的这些方法吗?

下面我们执行一下代码,并打下断点:

但是并没有走到下面这两个方法中的任何一个

事实上,通过alloc的流程分析我们可以类比到这里,LLVMisKindOfClass肯定进行了优化,使得isKindOfClass的调用被hook了。 通过在底层源码的全局搜索我们发现,还存在一个名为objc_opt_isKindOfClass的c语言函数,isKindOfClass的调用被hook到了objc_opt_isKindOfClass

objc_opt_isKindOfClass实现如下:

打个断点调试一下:

因为isKindOfClass的类方法和对象方法都是用传入的cls依次和self->isa()的继承链作对比,逻辑是一样的,所以优化后可以提高性能。

例题讲解

BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];

调用是类方法isKindOfClass,[NSObject class]是类NSObject,虽然NSObject和根元类不相同,但根元类继承自NSObject,所以结果为YES;

BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];

[NSObject class]的元类和NSObject显然不相同,所以结果为NO;

BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];

[LGPerson class]的元类和根元类的父类NSObject和LGPerson都不相同,结果为NO;

BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];

[LGPerson class]的元素和LGPerson不相同,结果为NO

BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];

[NSObject alloc]对象的类是NSObject,和NSObject相同,结果为YES

BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];

[NSObject alloc]的类和NSObject相同,结果为YES

BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];

[LGPerson alloc]对象的类LGPerson和LGPerson相同,结果为YES

BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];

[LGPerson alloc]对象的类和LGPerson相同,结果为YES