Runtime之MetaClass

对于iOS开发者来说, 元类 一直是一个在面试时被重点考察的点,为什么在语言设计时要增加 元类 这个概念?他的优点是什么?他到底有什么作用呢?这篇文章我们从Runtime源码的角度和语言设计的角度来探讨MetaClass存在的原因以及他的存在解决了哪些问题。

测试题

下面我们先看一下下面这个题目:

- (void)classJudge {
    Class ccls = [NSObject class];
    Class icls =  [Person class];
    
    NSLog(@"%@ isKindOfClass %@  result %@", NSStringFromClass(ccls),NSStringFromClass(ccls),@([ccls isKindOfClass:ccls]));
    NSLog(@"%@ isKindOfClass %@  result %@", NSStringFromClass(icls),NSStringFromClass(icls),@([icls isKindOfClass:icls]));
    NSLog(@"%@ isKindOfClass %@  result %@", NSStringFromClass(icls),NSStringFromClass(ccls),@([icls isKindOfClass:ccls]));
    
    NSLog(@"%@ isMemberOfClass %@  result %@", NSStringFromClass(ccls),NSStringFromClass(ccls),@([ccls isMemberOfClass:ccls]));
    NSLog(@"%@ isMemberOfClass %@  result %@", NSStringFromClass(icls),NSStringFromClass(icls),@([icls isMemberOfClass:icls]));
    NSLog(@"%@ isMemberOfClass %@  result %@", NSStringFromClass(icls),NSStringFromClass(ccls),@([icls isMemberOfClass:ccls]));

}

请问上述代码的执行结果是什么?为什么?上面的代码中主要涉及到三个方法

  • +class 类方法
  • isKindOfClass
  • isMemberOfClass

下面我们带着这些问题,看下上面这三个方法

Class 方法

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}

不过我们在NSObject.h中只发现了

- (Class)class

对象方法的声明,且当我们点击我们自己代码中的方法名进行跳转时也是跳转到了对应的对象方法中,因此我们可以知道在我们平时开发中调用class方法时,都是调用的对象方法(即使是使用类调用)。

我们看到对于对象方法中实际上是调用了object_getClass方法,因此我们继续看这个方法的实现:

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

这个方法是返回了当前对象的元类。

看完了方法的实现我们来分析下我们上面示例代码中获取到的类是什么:

Class ccls = [NSObject class];
Class icls =  [Person class];

根据源码我们很容易得出结论:ccls为NSObject类的元类 icls为Person类的元类

isKindOfClass

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

与Class方法相同,在NSObject.h文件中我们只发现了

- (BOOL)isKindOfClass:(Class)cls

一个方法的声明,因此我们重点分析下这个方法的实现:

  • 判断当前类与给定类是否相同
  • 如果不相同,则查找当前类的父类是否为给定类
  • 如果相同,则返回
  • 如果查找到最后都不等于给定类 则返回NO

结合我们上面的示例,我们认为

NSLog(@"%@ isKindOfClass %@  result %@", NSStringFromClass(ccls),NSStringFromClass(ccls),@([ccls isKindOfClass:ccls]));
  NSLog(@"%@ isKindOfClass %@  result %@", NSStringFromClass(icls),NSStringFromClass(icls),@([icls isKindOfClass:icls]));
  NSLog(@"%@ isKindOfClass %@  result %@", NSStringFromClass(icls),NSStringFromClass(ccls),@([icls isKindOfClass:ccls]));

的结果为:1 1 1

原因为:

  • 1和3均为相同的类 通过因此肯定是相等的
  • 2 因为Person类是继承自NSObject的因此这里也应该为1

isMemberOfClass

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

与前面两个方法相同,在NSObject.h文件中我们也是仅发现了对象方法的声明

- (BOOL)isMemberOfClass:(Class)aClass;

根据上述源码的实现,isMemberOfClass方法实际是:

  • 判断当前类的class是否为给给定类

在结合我们上面的代码示例

NSLog(@"%@ isMemberOfClass %@  result %@", NSStringFromClass(ccls),NSStringFromClass(ccls),@([ccls isMemberOfClass:ccls]));
 NSLog(@"%@ isMemberOfClass %@  result %@", NSStringFromClass(icls),NSStringFromClass(icls),@([icls isMemberOfClass:icls]));
 NSLog(@"%@ isMemberOfClass %@  result %@", NSStringFromClass(icls),NSStringFromClass(ccls),@([icls isMemberOfClass:ccls]));

这里我们判断输出结果应该为: 1 1 1

结果分析

那么我们带着上面的猜测,确认打印输出:

2021-01-01 19:26:08.475701+0800 MetaClassDemo[5964:166878] NSObject isKindOfClass NSObject  result 1
2021-01-01 19:26:08.475858+0800 MetaClassDemo[5964:166878] Person isKindOfClass Person  result 0
2021-01-01 19:26:08.475963+0800 MetaClassDemo[5964:166878] Person isKindOfClass NSObject  result 1
2021-01-01 19:26:08.476068+0800 MetaClassDemo[5964:166878] NSObject isMemberOfClass NSObject  result 0
2021-01-01 19:26:08.476188+0800 MetaClassDemo[5964:166878] Person isMemberOfClass Person  result 0
2021-01-01 19:26:08.476315+0800 MetaClassDemo[5964:166878] Person isMemberOfClass NSObject  result 0

结果很意外,我们猜想的是所有结果都为1,但是结果 2、4、5、6的结果均为0,那么我们来分析下原因:

isKindOfClass

Class ccls = [NSObject class];
Class icls =  [Person class];
NSLog(@"%@ isKindOfClass %@  result %@", NSStringFromClass(icls),NSStringFromClass(icls),@([icls isKindOfClass:icls]));

上面代码的结果为:0,明显我们知道Person类为NSObject类的子类,那么为何此处返回的是0呢?

我们在来看下isKindOfClass方法的实现:

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

我们发现

Class tcls = [self class];

这时候,tcls到底是到底是什么呢? 因为是对象方法因此这里调用到的是class对象方法,而Class的对象方法实现为:

- (Class)class {
    return object_getClass(self);
}
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

很明显这里我们得到的变量tcls实际上是类的元类,也就是说我们在for循环里遍历的是元类的superclass,那么for循环里的对比就很明显了 这里是用cls和元类的cls做对比肯定是不相同的。

那么问题又来了,为什么对于NSObject这个判断条件的结果是正确的呢?

这里我们就要贴出我们很熟悉的一张图来解释下了?

对于我们的示例

首先对于

Class ccls = [NSObject class];
Class icls =  [Person class];
NSLog(@"%@ isKindOfClass %@  result %@", NSStringFromClass(icls),NSStringFromClass(icls),@([icls isKindOfClass:icls]));

我们在isKindOfClass方法中实际上对比的是

if (icls == object_getClass(self)) {
    // icls 是否等于icls的metacls
}

对于NSObject,NSObject的元类的superclass依然是NSObject(这里是一个闭环),因此对于NSObject来说上述代码比较结果为YES,而对于Person类对比Person类和Person的元类(以及元类的父类 RootClass为NSObject)是否相等答案显然是NO。

而对于

[Person Class] == object_getClass([NSObject class]) 

我们实际上比较的是NSobject类和Person的元类的父类(一直向上搜索)即NSObject类。而对于

[Person Class] == object_getClass([Person class]) 

实际比较的是Person类和Person类的元类的父类(一直向上搜索)即NSObject类 因此这里返回的是NO。

isMemberOfClass

根据isKindOfClass的提示我们来重新分析下isMemberOfClass方法的实现:

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

从上面的分析我们已经知道我们实际上调用的是对象方法 - (BOOL)isMemberOfClass:(Class)cls ,我们发现isMemberOfClass的判断条件为

[self class] == cls

对于下面这几种情况

NSLog(@"%@ isMemberOfClass %@  result %@", NSStringFromClass(ccls),NSStringFromClass(ccls),@([ccls isMemberOfClass:ccls]));

NSLog(@"%@ isMemberOfClass %@  result %@", NSStringFromClass(icls),NSStringFromClass(icls),@([icls isMemberOfClass:icls]));

NSLog(@"%@ isMemberOfClass %@  result %@", NSStringFromClass(icls),NSStringFromClass(ccls),@([icls isMemberOfClass:ccls]));

  • NSObject == object_getClass([NSObject class]) 结果为NO
  • Person == object_getClass([Person class]) 结果为NO
  • Person == object_getClass([NSObject class]) 结果也为NO

我们发现实际上isMemberOfClass并没有像isKindOfClass那样递归的去查找父类,而是只判断了当前类。

总结

通过对上面题目的分析,我们了解了这两个重要方法的实现:

  • isKindOfClass

[A isKindOfClass: B];

B类是否为A类的元类或者A类元类的父类,这里因为NSObject的元类的superClass就是NSObject因此对于 [[NSObjec class] isKindOfClass:[NSObject class]] 返回是YES,更清晰的来说只有在判断某个类是否为NSObject类时这个条件才成立,为此我们特地做下面的测试:

Class ccls = [NSObject class];
Class icls =  [Person class];
Class scls =  [Female class];
    NSLog(@"%@ isKindOfClass %@  result %@", NSStringFromClass(icls),NSStringFromClass(ccls),@([icls isKindOfClass:ccls]));
    NSLog(@"%@ isKindOfClass %@  result %@", NSStringFromClass(scls),NSStringFromClass(icls),@([scls isKindOfClass:icls]));

结果:1,0。

这也验证了我们上面的结论

  • isMemberOfClass

[A isMemberOfClass: B]

B 类是否为A类元类 对于类方法的调用中这个条件是永远都不成立的。无论A与B之间是否存在继承或者被继承关系。

  • 对象方法

实际我们平时开发中最常用的是

- (void)instanceTest {
    Female *f = [Female new];
    
    NSLog(@"%@ isKindOfClass %@  result %@", NSStringFromClass([f class]),NSStringFromClass([Person class]),@([f isKindOfClass:[Person class]]));
    NSLog(@"%@ isMemberOfClass %@  result %@", NSStringFromClass([f class]),NSStringFromClass([Person class]),@([f isMemberOfClass:[Person class]]));
    NSLog(@"%@ isMemberOfClass %@  result %@", NSStringFromClass([f class]),NSStringFromClass([Female class]),@([f isMemberOfClass:[Female class]]));

}

打印结果:

2021-01-01 23:27:53.040093+0800 MetaClassDemo[8537:312528] Female isKindOfClass Person  result 1
2021-01-01 23:27:53.040223+0800 MetaClassDemo[8537:312528] Female isMemberOfClass Person  result 0
2021-01-01 23:27:53.040337+0800 MetaClassDemo[8537:312528] Female isMemberOfClass Female  result 1

这里我们套用上面的结论:

  • 对于isKindOfClass 我们比较的是对象f的元类(即Female类)及其父类是否为Person类 很显然返回值为YES
  • 对于isMemberOfClass 我们比较的是f的元类是否为Person类 很明显这里应该是Female类因此第二行输出NO,第三行输出YES。