1. 面试题代码示例
关于isKindOfClass:和isMemberOfClass:有一个很经典的面试题,主要考验对于isa走位链和superclass走位链的理解以及汇编调试、源码调试的动手能力,接下来就好好分析下这个面试题,一定要看到最后,常规分析里的isKindOfClass:是错误的。
代码示例:
// XJPerson 继承于 NSObject
void xjKindofDemo(void){
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re3 = [(id)[XJPerson class] isKindOfClass:[XJPerson class]];
BOOL re4 = [(id)[XJPerson class] isMemberOfClass:[XJPerson class]];
NSLog(@" \n 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)[XJPerson alloc] isKindOfClass:[XJPerson class]];
BOOL re8 = [(id)[XJPerson alloc] isMemberOfClass:[XJPerson class]];
NSLog(@" \n re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
***************************** 运行结果 *****************************
2021-06-24 14:16:22.361564+0800
re1 :1
re2 :0
re3 :0
re4 :0
2021-06-24 14:16:22.361631+0800
re5 :1
re6 :1
re7 :1
re8 :1
}
2.常规分析(错误)
objc4-818.2源码里这4个相关方法的定义如下:
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
+ (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;
}
分析:
-
[(id)[NSObject class] isKindOfClass:[NSObject class]]调用+ (BOOL)isKindOfClass:(Class)cls方法,第一次拿类NSObject(Class)的isa也就是根元类NSObject(Root Meta Class)与类NSObject(Class)比较,不相等;第二次拿根元类NSObject(Root Meta Class)的父类即类NSObject(Class)与类NSObject(Class)比较,相等,返回YES。 -
[(id)[NSObject class] isMemberOfClass:[NSObject class]]调用+ (BOOL)isMemberOfClass:(Class)cls方法,比较类NSObject(Class)的isa也就是根元类NSObject(Root Meta Class)与类NSObject(Class),不相等,返回NO。 -
[(id)[XJPerson class] isKindOfClass:[XJPerson class]]调用+ (BOOL)isKindOfClass:(Class)cls方法,第一次拿类XJPerson(Class)的isa也就是元类XJPerson(Meta Class)与类XJPerson(Class)比较,不相等;第二次拿XJPerson(Meta Class)的父类也就是根源类NSObject(Root Meta Class)与类XJPerson(Class)比较,不相等;第三次拿根元类NSObject(Root Meta Class)的父类即根类NSObject(Class)与类XJPerson(Class)比较,不相等。第四次获取根类NSObject(Class)的父类为nil,不满足判断条件,退出for循环,返回NO。 -
[(id)[XJPerson class] isMemberOfClass:[XJPerson class]]调用+ (BOOL)isMemberOfClass:(Class)cls方法,比较类XJPerson(Class)的isa也就是元类XJPerson(Meta Class)与类XJPerson(Class),不相等,返回NO。 -
[(id)[NSObject alloc] isKindOfClass:[NSObject class]]调用- (BOOL)isKindOfClass:(Class)cls方法,第一次拿对象[NSObject alloc]的isa也就是类NSObject(Class)与类NSObject(Class)比较,相等,返回YES。 -
[(id)[NSObject alloc] isMemberOfClass:[NSObject class]]调用- (BOOL)isMemberOfClass:(Class)cls方法,比较对象[NSObject alloc]的类NSObject(Class)与类NSObject(Class),相等。返回YES。 -
[(id)[XJPerson alloc] isKindOfClass:[XJPerson class]]调用- (BOOL)isKindOfClass:(Class)cls方法,第一次拿对象[XJPerson alloc]的isa也就是类XJPerson(Class)与类XJPerson(Class)比较,相等,返回YES。 -
[(id)[XJPerson alloc] isMemberOfClass:[XJPerson class]]调用- (BOOL)isMemberOfClass:(Class)cls方法,比较对象[XJPerson alloc]的类XJPerson(Class)与类XJPerson(Class),相等,返回YES。
分析的结果与代码运行的结果一样,但是如果在源码里的4个方法处打上断点,你会发现+ (BOOL)isKindOfClass:(Class)cls和- (BOOL)isKindOfClass:(Class)cls这两个方法压根就不会走,意不意外,惊不惊喜。
3. 汇编分析(正确)
添加断点,打开汇编调试,运行源码,会发现isKindOfClass:方法调用的是objc_opt_isKindOfClass函数。
源码里搜索objc_opt_isKindOfClass函数,找到其定义代码,关掉汇编调试,打上断点,运行源码,发现isKindOfClass:方法确实被重定向到了objc_opt_isKindOfClass函数。
从objc_opt_isKindOfClass函数源码可以看出来objc2.0会优先走上面,下面根据此函数来分析案例里的判断流程(只分析isKindOfClass:,isMemberOfClass:上面分析是对的)。
分析:
[(id)[NSObject class] isKindOfClass:[NSObject class]]调用BOOL objc_opt_isKindOfClass(id obj, Class otherClass)函数,第一次拿类NSObject(Class)的isa也就是根元类NSObject(Root Meta Class)与类NSObject(Class)比较,不相等;第二次拿根元类NSObject(Root Meta Class)的父类即类NSObject(Class)与类NSObject(Class)比较,相等,返回YES。[(id)[XJPerson class] isKindOfClass:[XJPerson class]]调用BOOL objc_opt_isKindOfClass(id obj, Class otherClass)函数,第一次拿类XJPerson(Class)的isa也就是元类XJPerson(Meta Class)与类XJPerson(Class)比较,不相等;第二次拿XJPerson(Meta Class)的父类也就是根源类NSObject(Root Meta Class)与类XJPerson(Class)比较,不相等;第三次拿根元类NSObject(Root Meta Class)的父类即根类NSObject(Class)与类XJPerson(Class)比较,不相等。第四次获取根类NSObject(Class)的父类为nil,不满足判断条件,退出for循环,返回NO。[(id)[NSObject alloc] isKindOfClass:[NSObject class]]调用BOOL objc_opt_isKindOfClass(id obj, Class otherClass)函数,第一次拿对象[NSObject alloc]的isa也就是类NSObject(Class)与类NSObject(Class)比较,相等,返回YES。[(id)[XJPerson alloc] isKindOfClass:[XJPerson class]]调用BOOL objc_opt_isKindOfClass(id obj, Class otherClass)函数,第一次拿对象[XJPerson alloc]的isa也就是类XJPerson(Class)与类XJPerson(Class)比较,相等,返回YES。
经过上面分析,得到的结果与程序运行的结果也一致,但是需要注意+ (BOOL)isKindOfClass:(Class)cls方法和- (BOOL)isKindOfClass:(Class)cls实际上都被重定向到objc_opt_isKindOfClass函数了。
实践是检验一切的标准。