isKindOfClass 和 isMemberOfClass

日常开发中,经常使用-isKindOfClass来判断对象是否是某个类或其父类(继承链上的类),但很少使用-isMemberOfClass,它们之间到底有什么不同,本篇文章就从objc源码来剖析它们的区别。

一、案例

objc源码(请参考这篇文章)中新建Person类,继承自NSObjec,空实现,然后main函数修改如下:

// main.m
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Class objCls = [NSObject class];
        BOOL ret1 = [(id)objCls isKindOfClass:[NSObject class]];
        BOOL ret2 = [(id)objCls isMemberOfClass:[NSObject class]];
        NSLog(@"\nret1 = %d\nret2 = %d", ret1, ret2);
        
        Class pCls = [Person class];
        BOOL ret3 = [(id)pCls isKindOfClass:[Person class]];
        BOOL ret4 = [(id)pCls isMemberOfClass:[Person class]];
        NSLog(@"\nret3 = %d\nret4 = %d", ret3, ret4);
        
        NSObject *obj = [[NSObject alloc] init];
        BOOL ret5 = [obj isKindOfClass:[NSObject class]];
        BOOL ret6 = [obj isMemberOfClass:[NSObject class]];
        NSLog(@"\nret5 = %d\nret6 = %d", ret5, ret6);
        
        Person *person = [[Person alloc] init];
        BOOL ret7 = [person isKindOfClass:[Person class]];
        BOOL ret8 = [person isMemberOfClass:[Person class]];
        NSLog(@"\nret7 = %d\nret8 = %d", ret7, ret8);

    }
    return 0;
}
复制代码

运行程序后,控制台输出如下:

2020-02-08 20:27:45.681409+0800 objc-debug[1378:33202] 
ret1 = 1
ret2 = 0
2020-02-08 20:27:45.682017+0800 objc-debug[1378:33202] 
ret3 = 0
ret4 = 0
2020-02-08 20:27:45.682176+0800 objc-debug[1378:33202] 
ret5 = 1
ret6 = 1
2020-02-08 20:27:45.682305+0800 objc-debug[1378:33202] 
ret7 = 1
ret8 = 1
Program ended with exit code: 0
复制代码

这个结果似乎有点奇怪,请先细细品味一番...


二、源码解读

上面的代码,实际调用了四个方法,类方法:

  • +isKindOfClass:
  • +isMemberOfClass:

和实例方法:

  • -isKindOfClass:
  • -isMemberOfClass:

1 +isKindOfClass:

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
复制代码

object_getClass(self)self->isa的指向,从源码可以看出,[NSObject class]就是NSObject类本身,其isa指向是NSObject元类,然后沿着NSObject元类的继承链向上逐级判断,NSObject元类是继承自NSObject的。因此,

BOOL ret1 = [(id)objCls isKindOfClass:[NSObject class]]
复制代码

这行代码的判断路径是NSObject元类->NSObject,自己和自己相等,所以ret1 = YES

Person->isaPerson元类Person元类继承自NSObject元类NSObject元类继承自NSObject,那么

BOOL ret3 = [(id)pCls isKindOfClass:[Person class]];
复制代码

这行代码的判断路径是Person元类->NSObject元类->NSObject,即Person != NSObjectret3 = NO

2 +isMemberOfClass:

+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}
复制代码

直接判断isa的指向是否和入参相等,NSObject元类自然不等于NSObject,所以ret2 = NOPerson元类也不等于Personret4 = NO

3 -isKindOfClass:

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

这和类方法唯一的区别就是循环条件的初始值是tcls = [self class],而不是tcls = object_getClass(self)

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}
复制代码

class方法也分类方法和实例方法,类方法直接返回self,实例方法返回的是self->isa的指向。此处我们是用实例对象调用的,所以会执行实例方法-class,这样以来,其实和类方法+isKindOfClass:的实现时相同的。

BOOL ret5 = [obj isKindOfClass:[NSObject class]];
复制代码

obj->isa就是NSObject,这行代码的判断路径是NSObject->nil,第一步就命中,ret5 = YES

BOOL ret7 = [person isKindOfClass:[Person class]];
复制代码

person->isa就是Person,这行代码的判断路径是Person->NSObject->nilret7 = YES

4 -isMemberOfClass:

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

即判断self->isa是否等于入参。

BOOL ret6 = [obj isMemberOfClass:[NSObject class]];
复制代码

这行代码翻译过来其实是obj->isa == NSObjectret6 = YES

BOOL ret8 = [person isMemberOfClass:[Person class]];
复制代码

同理,person->isa == Personret8 = YES

三、总结

+isKindOfClass:-isKindOfClass:会从self->isa的指向开始判断,沿着self->isa的继承链一直到NSObject,其中任一个和入参相等,就返回YES
+isMemberOfClass:-isMemberOfClass:直接判断self->isa是否和入参相等。
最后放上这张经典的isa指向和继承关系图:

猜你喜欢

转载自juejin.im/post/7084154382903672840