再探OC对象模型(isa/superclass)--从NSObject的实现出发

了解Objective-C中类型结构后,我们知道了OC的对象模型构成。在此讨论提出一个问题:object_getClass(obj)与[obj class]有何区别?

下面,我们将从isKindOfClass和isMemberOfClass两个方法入手进行探究。

背景

Objective-C中对于类的数据结构,分为了实例(object)、类(class)、元类(meta class)、根元类(root meta class)几种(也可加上根类(root class))。在他们的层次结构中,isa扮演者指向其父类型的职责。具体见这张经典的图:

OC对象模型

Runtime中为我们提供了object_getClass()函数,而NSObject为我们提供了+/- (Class)class两种方法。那么,这两种函数是否相同呢?我们来使用isKindOfClass和isMemberOfClass验证:

1
2
3
4
5
NSObject *obj = [[NSObject alloc] init];
NSLog(@"%p", obj);
NSLog(@"%p", object_getClass(obj));
NSLog(@"%p", object_getClass([NSObject class]));
NSLog(@"%p", [NSObject class]);

打印如下

1
2
3
4
0x10053a440                     // [[NSObject alloc] init]
0x7fffaa313140 // object_getClass(obj)
0x7fffaa3130f0 // object_getClass([NSObject class])
0x7fffaa313140 // [NSObject class]

可以看到,object_getClass(obj)与[NSObject class]是同一地址,说明对实例调用object_getClass获取了其类(),而对其类调用class类方法也是获取其类(这个”类”是指Runtime对象模型中的”类”)。

就这样就结束探究了吗,当然不会,下面的代码结果如何呢?

1
2
3
4
5
BOOL test1 = [[NSObject class] isKindOfClass:[NSObject class]];
BOOL test2 = [[NSObject class] isMemberOfClass:[NSObject class]];
BOOL test3 = [[SubClass class] isKindOfClass:[SubClass class]];
BOOL test4 = [[SubClass class] isMemberOfClass:[SubClass class]];
BOOL test5 = [[SubClass class] isSubclassOfClass:object_getClass([NSObject class])];

如果认为全为YES,那么这篇文章就有必要一看了。(不认为的话也有必要看看;P)
实际结果为: 仅第一个为YES,其余全为NO。为什么会这样呢?NSObject难道不是NSObject吗?SubClass明明继承了NSObject为什么又不是它的子类呢?

探究NSObject的实现

探究这个问题,我们首先从方法的实现入手,苹果开源了Runtime源码,所以我们可以查看NSObject的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// class方法
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}

// isMemberOfClass方法
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}

// isKindOfClass方法
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = class_getSuperclass(tcls)) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = class_getSuperclass(tcls)) {
if (tcls == cls) return YES;
}
return NO;
}

// isSubclassOfClass方法
+ (BOOL)isSubclassOfClass:(Class)cls {
for (Class tcls = self; tcls; tcls = class_getSuperclass(tcls)) {
if (tcls == cls) return YES;
}
return NO;
}

我们可以发现,NSObject的class方法实际提供了类方法和实例方法两种,而前者直接返回类,后者调用了object_getClass,也就是说class只会返回实例或对象对应的类(这个”类”是指Runtime对象模型中的”类”)。那么,从上面用代码结果不全为YES,则可以判断出object_getClass不是仅仅向class方法一样返回当前类这么简单(即使文档只写了这一特性)。

探究object_getClass()

现在我们来探究object_getClass的具体过程,在Runtime的类模型中,isa连接了几种类的结构,所以推断object_getClass是通过获取isa不断向上寻找的,所以出现了上面class方法与object_getClass方法的结果不一致的问题。我们来编写一下代码测试:

1
2
3
4
BOOL getObj1 = object_getClass([NSObject class]) == object_getClass(object_getClass([NSObject class]));
BOOL getObj2 = object_getClass([SubClass class]) == object_getClass(object_getClass(object_getClass([SubClass class])));
BOOL getObj3 = object_getClass([NSObject class]) == class_getSuperclass(object_getClass([NSObject class]));
BOOL getObj4 = object_getClass([NSObject class]) == object_getClass(class_getSuperclass(object_getClass([NSObject class])));

对于以上代码,如果我们的推测是正确的: object_getClass为按照isa遍历,class_getSuperclass为按照superclass遍历。那么按照上面所示的模型图,结果应该是(YES,NO,NO,YES)。

测试结果

结果果然如此,这说明: object_getClass为按照isa遍历,class_getSuperclass为按照superclass遍历。

那么我们刚才的代码在等号右侧显然是进行了如下过程:
getObj1: Root Class -> Meta Root Class -> Meta Root Class
getObj2: Class -> Meta Class -> Meta Root Class -> Meta Root Class
getObj3: Root Class -> Meta Root Class -> Root Class
getObj4: Root Class -> Meta Root Class -> Root Class -> Meta Root Class

这说明了这两个函数的遍历顺序,也加深了我们对于isa、superclass指针在OC对象模型的理解:比如子类通过isa会获取其元类,而通过元类isa直接获取到根元类,而非层层向上遍历。

问题解析

现在我们在回过头看刚才isKindOfClass和isMemberOfClass方法,就不难明白第一个试验中仅第一个为YES的原因了: 类或实例调用class方法只会获取该”类”,object_getClass会沿着isa链持续遍历,而isKindOfClass和isMemberOfClass的类方法正是采用了后者来遍历,所以返回的值。

其实说白了,文档没有告诉我们isKindOfClass和isMemberOfClass还有类方法,而它的类方法是持续向上级遍历的,实例方法则只是判断其”类”的。如下代码,使用实例方法结果是正确。

1
2
3
4
5
// 类方法  结果为NO
BOOL testOfCls = [[NSObject class] isMemberOfClass:[NSObject class]];
// 实例方法 结果为YES
NSObject *obj = [[NSObject alloc] init];
BOOL testOfObj = [obj isMemberOfClass:[NSObject class]];

总结

通过这一过程,我们加深了对Runtime对象模型层级的理解。同时,也总结一下class、isKindOfClass和isMemberOfClass方法各自的处理机制(以下加双引号的类指Runtime对象结构中的”类”概念):

class方法

  • 类方法: 返回该”类”。
  • 实例方法: 返回对象对应的”类”。

isKindOfClass方法

  • 类方法: 沿isa链向上遍历,判断是否与参数的isa链上任意层级的个体相同。
  • 实例方法: 判断是否与参数对应的”类”相同。

isMemberOfClass

  • 类方法: 判断该类的”元类”是否与参数相同。
  • 实例方法: 判断该实例的”类”是否与参数相同。

可以看出,类方法的处理机制并非字面理解的同级比较,也自然会出现[NSObject class] isMemberOfClass:[NSObject class]不对的问题。在使用时要特别注意,在理解OC对象模型的基础上使用。

相关资料

  1. Apple OpenSource - NSObject.mm