前言
在iOS面试中,经常会被问到关于isKindOfClass
和isMemberOfClass
相关的问题,比如下面的面试题:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL re3 = [(id)[ZZPerson class] isKindOfClass:[ZZPerson class]]; //
BOOL re4 = [(id)[ZZPerson class] isMemberOfClass:[ZZPerson class]]; //
NSLog(@" \nre1 :%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)[ZZPerson alloc] isKindOfClass:[ZZPerson class]]; //
BOOL re8 = [(id)[ZZPerson alloc] isMemberOfClass:[ZZPerson class]]; //
NSLog(@" \nre5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}
return 0;
}
2020-09-15 10:53:01.020760+0800 ZZObjc[78814:50146057]
re1 :1
re2 :0
re3 :0
re4 :0
2020-09-15 10:53:01.022779+0800 ZZObjc[78814:50146057]
re5 :1
re6 :1
re7 :1
re8 :1
Program ended with exit code: 0
这几行代码的输出结果为什么是这样呐?这里就需要弄清楚:
-
isKindOfClass
与isMemberOfClass
的区别是什么? -
isKindOfClass
与isMemberOfClass
的本质?
带着问题我们开始一探究竟。
isKindOfClass
首先我们找到+ (BOOL)isKindOfClass:(Class)cls
的源码实现:
// 类对象 isKindOfClass : 类对象
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
即:【传入的类对象(cls
)】与【当前类对象isa
所指向的类】比较,是否相等,相等则返回YES
;如果不相等,再次取当前类对象与当前类对象isa
指向的类的superClass
比较是否相等,如果相等,返回YES
,否则直到循环结束返回NO
.
>>>for循环分析:
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
0. cls = NSObject tcls = NSObject(根元类)
1. cls = NSObject tcls = NSObject YES
BOOL re3 = [(id)[ZZPerson class] isKindOfClass:[ZZPerson class]];
0. cls = ZZPerson tcls = ZZPerson(元类)
1. cls = ZZPerson tcls = NSObject(根元类)
2. cls = ZZPerson tcls = NSObject(根类)
3. cls = ZZPerson tcls = nil NO;
- (BOOL)isKindOfClass:(Class)cls
的源码实现:
// 实例对象 isKindOfClass:类
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
即:【当前实例对象的isa
指向的类】是否等于【传入的类(cls
)】,如果相等,返回YES
;否则,【当前实例对象.isa->superclass】与传入的类比较,相等则YES
,否则NO
;
>>>for循环分析:
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
0. cls NSObject tcls = [self class]=(self->isa) = NSObject YES
BOOL re7 = [(id)[ZZPerson alloc] isKindOfClass:[ZZPerson class]];
0. cls ZZPerson tcls = [self class]=(self->isa) = ZZPerson YES
重点敲黑板:在我们实际操作中无论是在- (BOOL)isKindOfClass:(Class)cls
中下符号断点,还是在+ (BOOL)isKindOfClass:(Class)cls
中下符号断点,都不会进入,而是首先执行了以下方法:
// 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->superclass) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
如果满足__OBJC2__
,就执行__OBJC2__
的代码块,当不满足条件的时候才会通过objc_msgSend
,执行我们一开始看到的方法; 这里是LLVM
内部执行的结果,但目前对此也没有很好的解释,当从代码中的slowpath
,fastpath
看,这里应该是苹果的一些优化方式。
isMemberOfClass
+ (BOOL)isMemberOfClass:(Class)cls
// 类对象 isMemberOfClass:类对象
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
即:【当前类对象的的isa
指向的类】等于【传入的类(cls)】吗?
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
NSObject->isa = NSObject(根元类)
cls = NSObject
NSObject(根元类) == NSObject ? return NO;
- (BOOL)isMemberOfClass:(Class)cls
// 实例对象 isMemberOfClass: 类对象
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
即:【当前实例对象的isa
指向的类】等于【传入类(cls
)】吗?
BOOL re8 = [(id)[ZZPerson alloc] isMemberOfClass:[ZZPerson class]];
当前self = [ZZPerson alloc],self.class = ZZPerson
cls = ZZPerson;
ZZPerson == ZZPerson ? return YES;
总结
区别:从实现的源码,我们已经可以看出isKindOfClass
与isMemberOfClass
的区别。值的注意的是这里的两个方法都包换实例方法和类方法,所以在看到题目的时候要看清是实例对象与类对象比较还是类对象与类对象比较;
本质:对isa
和superclass
的走位认识,如图: