Class、isa、元类
声明
本文的所涉及到的源码是 objc4 源码,截止到写本文最新的是 objc4-750
这个版本。
Class
我们在学习面向对象的学习中,接触最多的就是类,那么在OC类是由Class类型来表示的,Class是用C的数据结构来表示的。
看一下 NSObject
的声明,在头文件中,如下图所示:
1 | @interface NSObject <NSObject> { |
可以看到:
1、NSObject
是实现了 <NSObject>
协议的。
2、NSObject
中有 Class
类型的 isa
成员变量,外界是无法访问的,另外 isa
指针可能在将来也会被隐藏起来(OBJC_ISA_AVAILABILITY标示了)。
继续看一下 Class
到底是什么?
在上面的文件中可以看到 Class
的定义,如下代码:
1 | typedef struct objc_class *Class; |
可以看出 Class
是一个指向 objc_class
的结构体指针,Objective-C
中的类是由 Class
类型来表示的,它实际上是一个指向 objc_class
结构体的指针。
在下面的头文件中看一下 objc_class
的定义,如下:
1 | struct objc_class : objc_object { |
可以看出,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 | struct objc_object { |
objc_object
是一个结构体,里面有个私有成员变量 isa
是 isa_t
类型的。
而 isa_t
是一个 union 类型的,如下代码:
1 | union isa_t { |
总之在OC中,类也是一个对象称之为类对象,根据凡是对象都有自己的类的原理,那么类对象的肯定存在自己的类,这个类就是元类(meta-class)。
元类
在说元类之前,先看一下下面的例子,创建一个 NSMutableDictionary
实例对象 dict
,即向 NSMutableDictionary
发送 alloc
和 init
消息。
1 | NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; |
上面代码的大概执行流程如下几个步骤:
1、先执行 [NSMutableDictionary alloc]
,但是 NSMutableDictionary
没有 +alloc
方法,于是再去父类NSObject
中查找该方法。
2、NSObject
响应 +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 | Class object_getClass(id obj) |
举个例子吧,示例如下:
1 | NSObject *obj = [NSObject new]; |
打印结果如下:
1 | obj : <NSObject: 0x600002b19d70>, ->0x600002b19d70: |
可以看出,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。
总之:
1、objc_getClass
参数是类名的字符串,返回的就是这个类的类对象。
2、object_getClass
参数是 id
类型,它返回的是这个 id
的 isa
指针所指向的Class;如果传参是Class,则返回该Class的meta-class。
在 NSObject.mm 中,可以看到 self 和 class 方法都要实例和类方法,class 方法返回的都是类对象。
1 | + (id)self |
所以,无论是类还是实例调用 class 方法,返回的都是同一个 class object,举例:
1 | Class objClz1 = [NSObject class]; |
输出结果是:
1 | objClz1: NSObject, ->0x10fa30f38 |
isa
下面的例子来源自 这里,感谢 kingizz’s blog,代码中 Son
是 Father
的子类,而 Father
是 NSObject
的子类。
1 | @interface Father : NSObject |
1 | @interface Son : Father |
我们结合下面这个图来理解一下,子类、父类、元类以及 isa 指针。
一个实例对象的 isa
指向对象所属的类,这个类的 isa
指向这个类的元类,而这个元类的 isa
又指向 NSObject
的元类,NSObject
的元类的 isa
指向其本身,最终形成形成一个完美的闭环。
在OC中,所有的对象都有一个 isa
指针,指向对象所属的类,类也是一个对象,类对象的 isa
指针指向类的元类。
参考文章
2、Objective-C Runtime(一)对象模型及类与元类
扫码关注,期待与你的交流~