iOS底层探索之isKindOfClass、isMemberOfClass

245 阅读6分钟

和谐学习!不急不躁!!我是你们的老朋友小青龙~

作为iOS一名开发人员,我们平时经常会用到==isKindOfClassisMemberOfClass,但是你知道它们是怎么使用的吗?以及在底层是如何实现的吗?OK,我们先上案例:

【友情提示,前面的源码分析先挖个坑~】

探索 - - - - - - - - - - - - ==

先创建两个类

#pragma mark -- 创建一个Person类,继承自NSObject
@interface Person:NSObject
@property (nonatomic,strong) NSString *name;
-(void)eatFood;
@end

@implementation Person
-(void)eatFood{}
@end

#pragma mark -- 创建一个Student类,继承自Person
@interface Student:Person
@property (nonatomic,strong) NSString *age;
-(void)run;
@end

@implementation Student
-(void)run{}
@end

接着进行比较

#pragma mark -------- ==比较 ---------
void compareWithDD(){
    NSObject *n1 = [[NSObject alloc] init];
    NSObject *n2 = [[NSObject alloc] init];
    
    Person *p1 = [[Person alloc] init];
    Person *p2 = [[Person alloc] init];
    
    Student *s1 = [[Student alloc] init];
    Student *s2 = [[Student alloc] init];
    // ==比较
    NSLog(@"\n-------- ==打印 --------");
    NSLog(@"n1 == n2  -->%d",n1 == n2);
    NSLog(@"p1 == p2  -->%d",p1 == p2);
    NSLog(@"s1 == s2  -->%d",s1 == s2);
    NSLog(@"");
    NSLog(@"n1.class == n2.class  -->%d",n1.class == n2.class);
    NSLog(@"p1.class == p2.class  -->%d",p1.class == p2.class);
    NSLog(@"s1.class == s2.class  -->%d",s1.class == s2.class);
}

#pragma mark -- main函数入口
int main(int argc, char * argv[]) {

    compareWithDD();
    ...
}

打印结果

image.png 我们发现同一个类的不同对象进行比较,==比较的结果是false。这是为什么呢?我们打印一下n1、n2的地址,以及它们指向的值的地址。

01.png
得出结论:==符号的比较,是两个指针地址的比较

探索 - - - - - - - - - - - - isKindOfClass

#pragma mark -------- isKindOfClass比较 ---------
void compareWithIsKindOfClass(void){
    NSObject *n1 = [[NSObject alloc] init];
    NSObject *n2 = [[NSObject alloc] init];
    Person *p1 = [[Person alloc] init];
    Student *s1 = [[Student alloc] init];
    Class objectClass = NSObject.class;
    Class studentClass = Student.class;
    //isKindOfClass比较
    NSLog(@"\n\n-------- isKindOfClass打印 --------");
    NSLog(@"n1  isKindOfClass  n2.class  -->%d",[n1 isKindOfClass:n2.class]);
    NSLog(@"n1  isKindOfClass  p1.class  -->%d",[n1 isKindOfClass:p1.class]);
    NSLog(@"n1  isKindOfClass  s1.class  -->%d",[n1 isKindOfClass:s1.class]);
    NSLog(@"p1  isKindOfClass  s1.class  -->%d",[p1 isKindOfClass:s1.class]);
    NSLog(@"objectClass  isKindOfClass  objectClass  -->%d",[objectClass isKindOfClass:objectClass]);
    NSLog(@"studentClass  isKindOfClass  studentClass  -->%d",[studentClass isKindOfClass:studentClass]);
}

#pragma mark -- main函数入口
int main(int argc, char * argv[]) {

    compareWithIsKindOfClass();
    ...
}

打印结果

image.png

我们发现n1和n2.class地址不一样,但是结果是YES;NSObject和NSObject两个类进行比较,结果是true,Student和Student两个类进行比较,结果却是false,这是为什么呢?
接下来,我们来探究一下isKindOfClass的底层实现,老规矩,打开objc4-818.2源码,搜索“isKindOfClass”,找到:

/**
   类方法
  1、这是一个for循环;
  2、第一层循环:tcls等于self类的ISA指向(即元类),tcls是否等于传入的cls类,等于就返回YES,不等就进入下一次循环。
  2、第二层循环:tcls等于tcls的父类,tcls是否等于传入的cls类,等于就返回YES,不等就进入下一次循环。
  3、直到循环结束也没匹配到跟cls类相等的,返回NO;
*/
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

/**
   实例方法
  1、这是一个for循环;
  2、第一层循环:tcls等于self的父类,tcls是否等于传入的cls类,等于就返回YES,不等就进入下一次循环。
  3、第二层循环:tcls等于tcls的父类,tcls是否等于传入的cls类,等于就返回YES,不等就进入下一次循环。
  4、直到循环结束也没匹配到跟cls类相等的,返回NO;
*/
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

回到前面的疑问

  • n1和n2.class地址不一样,但是结果是YES;
n1是实例对象,会调用实例方法,进入循环:
第1遍--> tcls = [n1 class] ,跟n2.class一样,所以返回YES;
这也就解释通了n1和n2.class地址不一样,但结果是YES。
  • NSObject和NSObject两个类进行比较,结果是true,Student和Student两个类进行比较,结果却是false。
类->走类方法,进入循环:
NSObject走向:
第1遍-->tcls = NSObject根元类,NSObject根元类不等于NSObject类;
第2遍-->tcls = NSObject根元类的ISA指向(即NSObject类),所以返回YES;
Student走向:
第1遍-->tcls = Student元类,Student元类不等于Student类;
第2遍-->tcls = Student元类的ISA指向(即`NSObject根元类`),不等;
第3遍-->tcls = NSObject根元类的ISA指向(即`NSObject类`),不等;
第4遍-->tcls = NSObject根元类的ISA指向(即`nil`),循环结束,返回NO;

探索 - - - - - - - - - - - - isMemberOfClass

#pragma mark -------- isMemberOfClass比较 ---------
void compareWithIsMemberOfClass(void){
    NSObject *n1 = [[NSObject alloc] init];
    Person *p1 = [[Person alloc] init];
    Student *s1 = [[Student alloc] init];
    Class objectClass = NSObject.class;
    Class studentClass = Student.class;
    //isMemberOfClass比较
    NSLog(@"\n\n-------- isMemberOfClass打印 --------");
    NSLog(@"n1  isMemberOfClass  NSObject.class  -->%d",[n1 isMemberOfClass:NSObject.class]);
    NSLog(@"p1  isMemberOfClass  NSObject.class  -->%d",[p1 isMemberOfClass:NSObject.class]);
    NSLog(@"s1  isMemberOfClass  NSObject.class  -->%d",[s1 isMemberOfClass:NSObject.class]);
    NSLog(@"");
    NSLog(@"n1  isMemberOfClass  Person  -->%d",[n1 isMemberOfClass:Person.class]);
    NSLog(@"p1  isMemberOfClass  Person  -->%d",[p1 isMemberOfClass:Person.class]);
    NSLog(@"s1  isMemberOfClass  Person  -->%d",[s1 isMemberOfClass:Person.class]);
    NSLog(@"");
    NSLog(@"n1  isMemberOfClass  Student  -->%d",[n1 isMemberOfClass:Student.class]);
    NSLog(@"p1  isMemberOfClass  Student  -->%d",[p1 isMemberOfClass:Student.class]);
    NSLog(@"s1  isMemberOfClass  Student  -->%d",[s1 isMemberOfClass:Student.class]);
    NSLog(@"");
    NSLog(@"objectClass  isMemberOfClass  objectClass  -->%d",[objectClass isMemberOfClass:objectClass]);
    NSLog(@"studentClass  isMemberOfClass  studentClass  -->%d",[studentClass isMemberOfClass:studentClass]);
}

#pragma mark -- main函数入口
int main(int argc, char * argv[]) {
    
    compareWithIsMemberOfClass();
    ...
}

打印结果

image.png 擦看底层实现--> 打开objc4-818.2源码,搜索“isMemberOfClass”,找到:

/**
返回self元类和cls类的比较结果
*/
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

/**
返回self类和cls类的比较结果
*/
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

我们可以看到,isMemberOfClassisKindOfClass有点类似,只不过isMemberOfClass不走循环,只执行一次。

总结 - - - - - - - - - - - -

  • 如果只是比较a和b的值,用 ==
  • 关于isMemberOfClass的使用,如果是实例方法,走的是getSuperclass继承链;如果是类方法,走的是ISA指向链;
  • 如果要判断实例对象a,是否为b类的实例对象,用isKindOfClass 百度网盘
    objc4-818.2源码:pan.baidu.com/s/1qWbB4c7l…
    密码:vkh4
    Demo链接:pan.baidu.com/s/1mdlJXc_6…
    密码:9pds

注意 - - - - - - - - - - - -

别急着走,自己挖的坑还是要填一下的。

一顿操作下来,似乎没什么毛病,结论也可以解释我们的案例打印结果。
但是我们知道,oc代码到了底层,最终的实现是汇编,所以我们为了双重保险,应该先打开汇编调试--> main方法加入测试代码:

int main(int argc, const char * argv[]) {
    ...
    ///探索isKindof底层实现
    BOOL res = [NSObject.class isKindOfClass:NSObject.class];
    NSLog(@"%d",res);
    ...
    return 0;
}

BOOL res = [NSObject.class isKindOfClass:NSObject.class];这行打上断点,
打开汇编调试:Xcode -> Debug -> Debug Workflow -> Always show disassembly。 运行代码可以看到:

image.png

真是惊呆了我的小伙伴!底层实现竟然不是isKindOfClass:

OK,我们在objc源码搜索“objc_opt_isKindOfClass”,找到:

// 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->getSuperclass()) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}

/** 解释一下流程走向(结合isa走位图分析)
 (ps:因为当前是OJBC2版本,所以endif那行代码可以忽略)
  1、cls = obj的Isa指向,判断cls或cls的父类是否有isKindOf方法,有就进入第2步;
  2、这是一个for循环,
    第一遍循环:tcls等于cls,tcls是否等于传入的otherClass类,等于就返回YES,不等就进入下一次循环。
    第二遍循环:tcls等于tcls的父类,tcls是否等于传入的otherClass类,等于就返回YES,不等就进入下一次循环。
  3、直到循环结束也没匹配到跟otherClass类相等的,返回NO;
*/

废话连篇 - - - - - - - - - - - -

其实小编一开始也是想当然的认为,底层是isKindOf:
毕竟在objc源码工程里,确实存在着isKindOf:的类方法和实例方向,按照isa的指向链类的继承链的确可以解释的通,直到被-->库细大佬一句话否定了分析思路,我清晰的记得他老人家那句“要敢于质疑一切”。最后还是老老实实的走了一下汇编调试。