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->isa是Person元类,Person元类继承自NSObject元类,NSObject元类继承自NSObject,那么
BOOL ret3 = [(id)pCls isKindOfClass:[Person class]];
这行代码的判断路径是Person元类->NSObject元类->NSObject,即Person != NSObject,ret3 = NO。
2 +isMemberOfClass:
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
直接判断isa的指向是否和入参相等,NSObject元类自然不等于NSObject,所以ret2 = NO。Person元类也不等于Person,ret4 = 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->nil。ret7 = YES
4 -isMemberOfClass:
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
即判断self->isa是否等于入参。
BOOL ret6 = [obj isMemberOfClass:[NSObject class]];
这行代码翻译过来其实是obj->isa == NSObject,ret6 = YES。
BOOL ret8 = [person isMemberOfClass:[Person class]];
同理,person->isa == Person,ret8 = YES。
三、总结
+isKindOfClass:和-isKindOfClass:会从self->isa的指向开始判断,沿着self->isa的继承链一直到NSObject,其中任一个和入参相等,就返回YES。
+isMemberOfClass:和-isMemberOfClass:直接判断self->isa是否和入参相等。
最后放上这张经典的isa指向和继承关系图:

转载自:https://juejin.cn/post/7084154382903672840