Class、isa、元类

声明

本文的所涉及到的源码是 objc4 源码,截止到写本文最新的是 objc4-750 这个版本。

Class

我们在学习面向对象的学习中,接触最多的就是类,那么在OC类是由Class类型来表示的,Class是用C的数据结构来表示的。

看一下 NSObject 的声明,在头文件中,如下图所示:

1
2
3
4
5
6
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

可以看到:
1、NSObject 是实现了 <NSObject> 协议的。
2、NSObject 中有 Class 类型的 isa 成员变量,外界是无法访问的,另外 isa 指针可能在将来也会被隐藏起来(OBJC_ISA_AVAILABILITY标示了)。

继续看一下 Class 到底是什么?

在上面的文件中可以看到 Class 的定义,如下代码:

1
2
3
typedef struct objc_class *Class;
typedef struct objc_object *id;

可以看出 Class 是一个指向 objc_class 的结构体指针,Objective-C 中的类是由 Class 类型来表示的,它实际上是一个指向 objc_class 结构体的指针。

在下面的头文件中看一下 objc_class 的定义,如下:

1
2
3
4
5
6
7
8
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
// ....
}

可以看出,objc_class 用来描述OC中的类,而 objc_object 用来描述OC中的对象,类(objc_class)其实也是一个对象(objc_object),另外 id 是代表对象的,它是指向 objc_object 的结构体指针,它的存在可以让我们实现类似于C++中泛型的一些操作。该类型的对象可以转换为任何一种对象,有点类似于C语言中 void * 指针类型的作用。

这里要注意,objc_class 的定义在 objc-runtime-old.h中和 objc-runtime-new.h 中的不一样。这里以 objc-runtime-new.h 为主,建议可以看看 被误解的 objc_class 这篇文章。

再来看一下 objc_object,如下图所示:

1
2
3
4
5
struct objc_object {
private:
isa_t isa;
// ...
}

objc_object 是一个结构体,里面有个私有成员变量 isaisa_t 类型的。

isa_t 是一个 union 类型的,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};

总之在OC中,类也是一个对象称之为类对象,根据凡是对象都有自己的类的原理,那么类对象的肯定存在自己的类,这个类就是元类(meta-class)。

元类

在说元类之前,先看一下下面的例子,创建一个 NSMutableDictionary 实例对象 dict,即向 NSMutableDictionary 发送 allocinit 消息。

1
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];

上面代码的大概执行流程如下几个步骤:

1、先执行 [NSMutableDictionary alloc],但是 NSMutableDictionary 没有 +alloc 方法,于是再去父类NSObject 中查找该方法。

2NSObject 响应 +alloc 方法,开始检测 NSMutableDictionary 类,并根据其所需的内存空间大小开始分配内存空间,然后把 isa 指针指向 NSMutableDictionary 类。同时,+alloc 也被加进 cache 列表里面。

3、接着,执行 -init 方法,如果 NSMutableDictionary 响应该方法,则直接将其加入 cache,如果不响应,则去父类查找。

4、在后期的操作中,如果再以 [[NSMutableDictionary alloc] init] 这种方式来创建字典对象,则会直接从 cache 中取出相应的方法,直接调用。

上面是创建一个实例对象的大致流程,接下来我们说说元类。

元类简单来说就是类对象的类。类描述的是对象,那么元类描述的就是Class类对象的类。元类定义了类的行为(类方法),在平时开发时,meta-class 基本是用不着接触的,但最好还是要知道它的存在,这样可以更好的理解OC的设计。

1
NSMutableDictionary *tDatas = [NSMutableDictionary dictionaryWithCapacity:5];

拿上面的示例来说,向 NSMutableDictionary 发送 dictionaryWithCapacity 这个消息的时候,Runtime 会在这个类的 meta-class 的方法列表中查找,通过 SEL 找到后取出方法中的 IMP 函数入口指针,并执行该方法,如果找不到就进行消息转发的流程中,最终可能会导致 Crash,消息转发的原理和机制可以参考 消息机制 这几篇文章。

元类保存了类方法的列表。当一个类方法被调用时,元类会首先查找它本身是否有该类方法的实现,如果没有则该元类会向它的父类查找该方法,直到一直找到继承链的头。

1
Class object_getClass(id obj);

object_getClass 可以获取一个对象的 class object,其源码实现如下:

1
2
3
4
5
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}

举个例子吧,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
NSObject *obj = [NSObject new];
Class obj1 = object_getClass(obj);
Class obj2 = object_getClass([NSObject class]);
Class obj3 = objc_getMetaClass("NSObject");
Class obj4 = object_getClass(obj1);
const char *name = [NSStringFromClass(obj1) UTF8String];
NSLog(@"name: %s", name); //name: NSObject
Class obj5 = objc_getMetaClass(name);
Class obj6 = objc_getClass(name);
NSLog(@"obj : %@, ->%p: ", obj, obj);
NSLog(@"obj1: %@, ->%p: ", obj1, obj1);
NSLog(@"obj2: %@, ->%p: ", obj2, obj2);
NSLog(@"obj3: %@, ->%p: ", obj3, obj3);
NSLog(@"obj4: %@, ->%p: ", obj4, obj4);
NSLog(@"obj5: %@, ->%p: ", obj5, obj5);
NSLog(@"obj6: %@, ->%p: ", obj6, obj6);

打印结果如下:

1
2
3
4
5
6
7
obj : <NSObject: 0x600002b19d70>, ->0x600002b19d70:
obj1: NSObject, ->0x10c96bf38:
obj2: NSObject, ->0x10c96bee8:
obj3: NSObject, ->0x10c96bee8:
obj4: NSObject, ->0x10c96bee8:
obj5: NSObject, ->0x10c96bee8:
obj6: NSObject, ->0x10c96bf38:

可以看出,obj 是一个实例对象,obj1和obj6是一个 class object,其二者地址也一致,obj2、obj3、obj4 和 obj5 都获取到的是元类。

通过类对象调用的 object_getClass 得到的是该类对象的 meta-class,如 obj2 和 obj4,而通过实例对象调用的object_getClass 得到的是该实例对象的类对象,如 obj1,objc_getClass 这个方法获取是实例对象的类对象,与object_getClass 还是有点不一样的。而 objc_getMetaClass 可以直接获取 meta-class,如 obj3。

NSObject.mm 中,可以看到 self 和 class 方法都要实例和类方法,class 方法返回的都是类对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+ (id)self
{
return (id)self;
}
- (id)self
{
return self;
}
+ (Class)class
{
return self;
}
- (Class)class
{
return object_getClass(self);
}

所以,无论是类还是实例调用 class 方法,返回的都是同一个 class object,举例:

1
2
3
4
5
6
Class objClz1 = [NSObject class];
Class objClz2 = [[[NSObject alloc] init] class];
if (objClz1 == objClz2) {
NSLog(@"objClz1: %@, ->%p", objClz1, objClz1);
}

输出结果是:

1
objClz1: NSObject, ->0x10fa30f38

isa

下面的例子来源自 这里,感谢 kingizz’s blog,代码中 SonFather 的子类,而 FatherNSObject 的子类。

1
2
3
@interface Father : NSObject
@end
1
2
3
@interface Son : Father
@end

我们结合下面这个图来理解一下,子类、父类、元类以及 isa 指针。

一个实例对象的 isa 指向对象所属的类,这个类的 isa 指向这个类的元类,而这个元类的 isa 又指向 NSObject 的元类,NSObject 的元类的 isa 指向其本身,最终形成形成一个完美的闭环。

在OC中,所有的对象都有一个 isa 指针,指向对象所属的类,类也是一个对象,类对象的 isa 指针指向类的元类。

参考文章

1、Objective-C 中的对象、类、元类

2、Objective-C Runtime(一)对象模型及类与元类

3、被误解的 objc_class


扫码关注,期待与你的交流~

坚持原创技术分享!