前几天看到老哥说他去面试,面试官问他OC里类对象和实例对象有什么区别?不知道Objective-C对象模型的同学很可能搞不清楚面试官究竟想问什么。
本篇先讲对象模型到底是什么样的以及runtime中,类是如何实现的。
Objective-C对象模型
我们打开
@interface NSObject {
Class isa OBJC_ISA_AVAILABILITY;
}
可以看到NSObject 就是一个包含 isa 指针的结构体,在 Objective-C 语言中,每一个类实际上也是一个对象。每一个类也有一个名为 isa 的指针。每一个类也可以接受消息,例如[NSObject alloc],就是向 NSObject 这个类发送名为alloc消息。
再看一下class的定义
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
发现他里面也有一个isa指针,所以类也是一个对象,那它也必须是另一个类的实列,这个类就是元类 (metaclass)。元类保存了类方法的列表。当一个类方法被调用时,元类会首先查找它本身是否有该类方法的实现,如果没有,则该元类会向它的父类查找该方法,直到一直找到继承链的头。
1.isa:需要注意的是在Objective-C中,所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,它指向metaClass(元类),我们会在后面介绍它。
2.super_class:指向该类的父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class为NULL。
3.cache:用于缓存最近使用的方法。一个接收者对象接收到一个消息时,它会根据isa指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,我们都是methodLists中遍历一遍,性能势必很差。这时,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才去methodLists中查找方法。这样,对于那些经常用到的方法的调用,但提高了调用的效率。
4.version:我们可以使用这个字段来提供类的版本信息。这对于对象的序列化非常有用,它可是让我们识别出不同类定义版本中实例变量布局的改变。
元类 (metaclass)
元类 (metaclass) 也是一个对象,那么元类的 isa 指针又指向哪里呢?为了设计上的完整,所有的元类的 isa 指针都会指向一个根元类 (root metaclass)。根元类 (root metaclass) 本身的 isa 指针指向自己,这样就行成了一个闭环。
我们再来看看继承关系,由于类方法的定义是保存在元类 (metaclass) 中,而方法调用的规则是,如果该类没有一个方法的实现,则向它的父类继续查找。所以,为了保证父类的类方法可以在子类中可以被调用,所以子类的元类会继承父类的元类,换而言之,类对象和元类对象有着同样的继承关系。如下图:
图中的Root Class 是指 NSObject,我们可以从图中看出:
1.NSObject 类包括它的对象实例方法。
2.NSObject 的元类包括它的类方法,例如 alloc 方法。
3.NSObject 的元类继承自 NSObject 类。
4.一个 NSObject 的类中的方法同时也会被 NSObject 的子类在查找方法时找到
为了验证上面说的内容,我做了以下几个测试,在这之前需要先了解几个函数具体的作用与实现方式。
+class 与 objc_getClass()
1.调用+class方法是无法获取meta-class,它只是返回类本身。
2.对类对象调用objc_getClass()可以获取它的meta-class。
-isKindOfClass和isMemberOfClass
+ (BOOL) isKindOfClass:(Class)class
{
Class r0 = object_getClass(self);
while (1) {
if (r0 == 0) {
return 0;
}else{
if (r0 != class) {
r0 = [r0 superclass];
}else{
return 1;
}
}
}
}
+ (BOOL)isMemberOfClass:(Class)class
{
return isa == (Class)aClass;
}
它们的内部实现大概是这个样子。可以看出:
isKindOfClass:确定一个对象是否是一个类的成员,或者是派生自该类的成员.
isMemberOfClass:确定一个对象是否是当前类的成员
验证例子1
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
输出结果:YES/NO/NO/NO。
1.[NSObject class]获取的NSObject类对象 在函数体内获取的是它的元类,而NSObject的元类的父类就是NSObject本身,所以第一个是YES。
2.因为是isMemberOfClass,所以元类不等于自己,返回NO
3.同上,获得的是元类,这里元类的父类与类对象不相等。
4.同上。
类的成员变量
如果把类的实例看成一个 C 语言的结构体(struct),上面说的 isa 指针就是这个结构体的第一个成员变量,而类的其它成员变量依次排列在结构体中。
验证例子2
@interface NSObject (test)
+ (void)foo;
- (void)foo
@end
@implementation NSObject (test)
- (void)foo {
NSLog(@"test result");
}
@end
// 测试代码
[NSObject foo];
[[NSObject new] foo];
测试结果:都正常的运行了-foo方法。
原因是NSObject的类方法保存在根元类中,根元类没有这个方法便向它的父类寻找,根元类的父类指向NSObject本身,而成员方法是保存在类中的,所以找到了这个方法。
可变与不可变
因为对象在内存中的排布可以看成一个结构体,该结构体的大小并不能动态变化。所以无法在运行时动态给对象增加成员变量。
相对的,对象的方法定义都保存在类的可变区域中。我们可以看到方法的定义列表是一个名为 methodLists的指针的指针(如下图所示)。通过修改该指针指向的指针的值,就可以实现动态地为某一个类增加成员方法。
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists <--- OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
Category
Category是表示一个指向分类的结构体的指针,其定义如下:
typedef struct objc_category *Category
struct objc_category{
char *category_name OBJC2_UNAVAILABLE; // 分类名
char *class_name OBJC2_UNAVAILABLE; // 分类所属的类名
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; // 实例方法列表
struct objc_method_list *class_methods OBJC2_UNAVAILABLE; // 类方法列表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 分类所实现的协议列表
}
这个结构体主要包含了分类定义的实例方法与类方法,其中instance_methods列表是objc_class中方法列表的一个子集,而class_methods列表是元类方法列表的一个子集。
可发现,类别中没有ivar成员变量指针,也就意味着:类别中不能够添加实例变量和属性
小结:
1.实例对象是类的对象,类对象是元类的对象。
2.类对象和元类对象有着同样的继承关系
3.成员方法保存在类中,类方法保存在元类中。
- Category包含对象方法列表和元类方法列表,但没有ivar成员变量指针。所以只能添加方法不能添加变量和属性。
5.调用类方法时,是先查找元类本身是否有该方法,如果没有,则元类向它的父类查找,而不是类对象的父类。
相关文章
神经病院objc runtime入院考试
Objective-C对象模型及应用
Objective-C Runtime 运行时之一:类与对象
初识 Objective-C Runtime
Runtime之类与对象总结