经典面试题:isKindOfClass:和isMemberOfClass:的分析

352 阅读4分钟

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;
}

image.png

分析:

  1. [(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

  2. [(id)[NSObject class] isMemberOfClass:[NSObject class]]调用+ (BOOL)isMemberOfClass:(Class)cls方法,比较类NSObject(Class)isa也就是根元类NSObject(Root Meta Class)与类NSObject(Class),不相等,返回NO

  3. [(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

  4. [(id)[XJPerson class] isMemberOfClass:[XJPerson class]]调用+ (BOOL)isMemberOfClass:(Class)cls方法,比较类XJPerson(Class)isa也就是元类XJPerson(Meta Class)与类XJPerson(Class),不相等,返回NO

  5. [(id)[NSObject alloc] isKindOfClass:[NSObject class]]调用- (BOOL)isKindOfClass:(Class)cls方法,第一次拿对象[NSObject alloc]isa也就是类NSObject(Class)与类NSObject(Class)比较,相等,返回YES

  6. [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]调用- (BOOL)isMemberOfClass:(Class)cls方法,比较对象[NSObject alloc]的类NSObject(Class)与类NSObject(Class),相等。返回YES

  7. [(id)[XJPerson alloc] isKindOfClass:[XJPerson class]]调用- (BOOL)isKindOfClass:(Class)cls方法,第一次拿对象[XJPerson alloc]isa也就是类XJPerson(Class)与类XJPerson(Class)比较,相等,返回YES

  8. [(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函数。

image.png image.png

源码里搜索objc_opt_isKindOfClass函数,找到其定义代码,关掉汇编调试,打上断点,运行源码,发现isKindOfClass:方法确实被重定向到了objc_opt_isKindOfClass函数。

image.png

objc_opt_isKindOfClass函数源码可以看出来objc2.0会优先走上面,下面根据此函数来分析案例里的判断流程(只分析isKindOfClass:isMemberOfClass:上面分析是对的)。

分析:

  1. [(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
  2. [(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
  3. [(id)[NSObject alloc] isKindOfClass:[NSObject class]]调用BOOL objc_opt_isKindOfClass(id obj, Class otherClass)函数,第一次拿对象[NSObject alloc]isa也就是类NSObject(Class)与类NSObject(Class)比较,相等,返回YES
  4. [(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函数了。

实践是检验一切的标准。