什么是元类
0 条参与在本文中,我们看一下Objective-C中一个陌生的概念 - 元类。Objective-C中的每个类都有自己相关的元类,但是由于我们很少直接使用元类,所以它们仍保持着神秘。我将首先来看看如何在运行时创建一个类。通过检查这个创建的“类对”,我将解释元类是什么,并且还涵盖了在Objective-C中将数据作为对象或类的意义更为一般的主题。
在运行时创建一个类
以下代码在运行时创建一个NSError
的子类,并为其添加了一个方法:
|
|
添加的方法使用名为ReportFunction的函数作为其实现,其定义如下:
|
|
表面上,这一切都很简单。在运行时创建一个类只有三个简单的步骤:
- 为“类对”分配存储空间(使用
objc_allocateClassPair
)。 - 根据需要向类添加方法和ivars(我已经使用
class_addMethod
添加了一个方法)。 - 注册类,以便可以使用(使用
objc_registerClassPair
)。
然而,马上会产生疑问:什么是“类对”?该函数objc_allocateClassPair
只返回一个值:该类。对的另一半在哪里?
我相信你猜到了这对的另一半是元类(这正是本文的标题),但是要解释这是什么,为什么需要它,我将给出一些关于Objective-C对象和类的背景。
将数据结构作为对象需要什么?
每个对象都有一个类。这是一个基本的面向对象的概念,但在Objective-C中,它也是数据的基本部分。任何具有指向正确位置的类的指针的数据结构都可以被视为一个对象。
在Objective-C中,对象的类由它的isa
指针决定。该isa
指针指向对象的类。
实际上,Objective-C中对象的基本定义如下所示:
|
|
这是什么意思:任何以指向Class结构的指针开始的结构都可以被视为一个objc_object
。
Objective-C中对象最重要的特征是可以向其发送消息:
|
|
这是因为当你向Objective-C对象(像这里的NSCFString
)发送消息时,运行时将根据对象的isa指针,以获取对象的Class
(在这里为NSCFString
类)。该Class
包含用于该类所有对象的方法列表和指向superclass
的指针以查找继承的方法。运行时通过查看Class
和superclass
上的方法列表来找到一个匹配的消息选择器(在上述例子里,writeToFile:atomically:encoding:error
方法在NSString
类中)。然后,运行时调用该方法的函数(IMP
)。
重点是Class
定义了可以发送给对象的消息。
什么是元类?
现在,你可能已经知道,Class
在Objective-C
中也是一个对象。这意味着你可以发送消息到Class
。
|
|
在这里,defaultStringEncoding
消息发送到NSString
类。
这是因为Objective-C中的每个Class
都是一个对象本身。这意味着Class
结构必须以isa
指针开始,以便它与objc_object
结构二进制兼容,结构中的下一个字段必须是指向superclass
的指针(或者是基类则为nil
)。
Class
有几种不同的定义,这取决于运行时的版本,但是可以确定,他们都是以一个isa
字段后跟一个superclass
字段。
|
|
但是,为了我们能调用Class
上的方法,Class
的isa
指针本身必须指向一个Class
结构,Class
结构必须包含可以在Class
上调用的Method
的列表。
由此可以得出元类的定义:元类是Class
对象的类。
简单的说:
- 当你向对象发送消息时,该消息将在对象类的方法列表中查找。
- 当你向类发送消息时,该消息将在类的元类的方法列表中查找。
元类是必不可少的,因为它存储了Class
的类方法。每个Class
都必须有唯一的元类,因为每个Class
都有一个潜在的唯一的类方法列表。
元类的类是什么?
元类,就像之前的Class
一样,也是一个对象。这意味着你也可以调用它的方法。当然,这意味着它也必须有一个类。
所有元类使用基类的元类(继承层次结构中顶级Class
的元类)作为它们的类。这就意味着所有从NSObject
继承的类(大多数类),它们元类的类为NSObject
的元类。
遵循所有元类使用基类的元类作为其类的规则,基元类将是其自己的类(它们的isa
指针指向自己)。这意味着NSObject
元类上的isa
指针指向自身(它本身就是一个实例)。
类和元类的继承
以同样的方式,Class
通过其super_class
指针指向父类,元类使用自己的super_class
指针指向Class
的父类的元类。
另一个奇怪的是,基类的元类将super_class
设置为基类,也就是说基类的元类继承自基类。
这种继承层次结构的结果是,层次结构中的所有实例,类和元类都继承自基类。
对于NSObject层次结构中的所有实例,类和元类,这意味着所有NSObject实例方法都是有效的。 对于类和元类,所有NSObject类的方法也是有效的。
这里的描述比较混乱。以下是来自格雷戈·帕克(Greg Parker)的一张图,完美的展示了它们的关系。
在实现中,Root Class 是指NSObject
,我们可以从图中看出:
NSObject
类包括它的对象实例方法。NSObject
的元类包括它的类方法,例如alloc
方法。NSObject
的元类继承自 NSObject 类。- 一个
NSObject
的类中的方法同时也会被NSObject
的子类在查找方法时找到。
实验确认
要确认所有这一切,我们来看看文章开始时写的ReportFunction
的输出。这个函数的目的是跟踪isa指针并记录。
要运行ReportFunction
,我们需要创建一个实例,并调用report
方法。
|
|
既然没有声明report方法,那么就使用performSelector:
调用它,所以编译器不会发出警告。
ReportFunction
将遍历isa
指针并打印对象的类,元类和类的元类。
获取对象的类:
ReportFunction
使用object_getClass
获取isa
指针,是因为isa
指针是类受保护成员(你不能直接访问对象的isa
指针)。ReportFunction
不使用class
方法,因为调用Class
对象上的class
方法不返回元类,而是再次返回Class
(所以[NSString class]
将返回NSString
类而不是NSString
元类)。
以下是程序运行的输出(去掉了NSLog前缀):
|
|
查看打印isa
得到的地址:
- 对象是地址
0x10010c810
。 - 该类是地址
0x10010c600
。 - 元类是地址
0x10010c630
。 - 元类的类(即NSObject元类)是地址
0x7fff71038480
。 - NSObject元类的类就是本身。
地址的值不是很重要,它只是显示了从类到元类再到NSObject
元类的过程。
结论
元类是Class
对象的类。每个Class
都有自己独特的元类(因为每个Class
都可以有自己独特的方法列表)。这意味着所有Class
对象本身都不是同一个类。
元类能确保Class
对象有所有底层类的实例和类方法,再加上所有自己的类方法。对于继承自NSObject
的类,这意味着所有的Class
(和元类)对象都定义了NSObject
的所有实例和协议方法。
所有元类都使用基类的元类(NSObject
的元类)作为它们的类,包括基元类,它是运行时中唯一的自循环定义类(它的元类指向自己)。