在本文中,我们看一下Objective-C中一个陌生的概念 - 元类。Objective-C中的每个类都有自己相关的元类,但是由于我们很少直接使用元类,所以它们仍保持着神秘。我将首先来看看如何在运行时创建一个类。通过检查这个创建的“类对”,我将解释元类是什么,并且还涵盖了在Objective-C中将数据作为对象或类的意义更为一般的主题。

在运行时创建一个类

以下代码在运行时创建一个NSError的子类,并为其添加了一个方法:

1
2
3
Class newClass = objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", 0);
class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:");
objc_registerClassPair(newClass);

添加的方法使用名为ReportFunction的函数作为其实现,其定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
void ReportFunction(id self, SEL _cmd) {
NSLog(@"This object is %p.", self);
NSLog(@"Class is %@, and super is %@.", [self class], [self superclass]);
Class currentClass = [self class];
for (int i = 1; i < 5; i++) {
NSLog(@"Following the isa pointer %d times gives %p", i, currentClass);
currentClass = object_getClass(currentClass);
}
NSLog(@"NSObject's class is %p", [NSObject class]);
NSLog(@"NSObject's meta class is %p", object_getClass([NSObject class]));
}

表面上,这一切都很简单。在运行时创建一个类只有三个简单的步骤:

  1. 为“类对”分配存储空间(使用objc_allocateClassPair)。
  2. 根据需要向类添加方法和ivars(我已经使用class_addMethod添加了一个方法)。
  3. 注册类,以便可以使用(使用objc_registerClassPair)。

然而,马上会产生疑问:什么是“类对”?该函数objc_allocateClassPair只返回一个值:该类。对的另一半在哪里?

我相信你猜到了这对的另一半是元类(这正是本文的标题),但是要解释这是什么,为什么需要它,我将给出一些关于Objective-C对象和类的背景。

将数据结构作为对象需要什么?

每个对象都有一个类。这是一个基本的面向对象的概念,但在Objective-C中,它也是数据的基本部分。任何具有指向正确位置的类的指针的数据结构都可以被视为一个对象。

在Objective-C中,对象的类由它的isa指针决定。该isa指针指向对象的类。

实际上,Objective-C中对象的基本定义如下所示:

1
2
3
typedef struct objc_object {
Class isa;
} *id;

这是什么意思:任何以指向Class结构的指针开始的结构都可以被视为一个objc_object

Objective-C中对象最重要的特征是可以向其发送消息:

1
[@"stringValue" writeToFile:@"/file.txt" atomically:YES encoding:NSUTF8StringEncoding error:NULL];

这是因为当你向Objective-C对象(像这里的NSCFString)发送消息时,运行时将根据对象的isa指针,以获取对象的Class(在这里为NSCFString类)。该Class包含用于该类所有对象的方法列表和指向superclass的指针以查找继承的方法。运行时通过查看Classsuperclass上的方法列表来找到一个匹配的消息选择器(在上述例子里,writeToFile:atomically:encoding:error方法在NSString类中)。然后,运行时调用该方法的函数(IMP)。

重点是Class定义了可以发送给对象的消息。

什么是元类?

现在,你可能已经知道,ClassObjective-C中也是一个对象。这意味着你可以发送消息到Class

1
NSStringEncoding defaultStringEncoding = [NSString defaultStringEncoding];

在这里,defaultStringEncoding消息发送到NSString类。

这是因为Objective-C中的每个Class都是一个对象本身。这意味着Class结构必须以isa指针开始,以便它与objc_object结构二进制兼容,结构中的下一个字段必须是指向superclass的指针(或者是基类则为nil)。

Class有几种不同的定义,这取决于运行时的版本,但是可以确定,他们都是以一个isa字段后跟一个superclass字段。

1
2
3
4
5
6
typedef struct objc_class *Class;
struct objc_class {
Class isa;
Class super_class;
/* followed by runtime specific details... */
};

但是,为了我们能调用Class上的方法,Classisa指针本身必须指向一个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,我们可以从图中看出:

  1. NSObject类包括它的对象实例方法。
  2. NSObject的元类包括它的类方法,例如alloc方法。
  3. NSObject的元类继承自 NSObject 类。
  4. 一个NSObject的类中的方法同时也会被NSObject的子类在查找方法时找到。

实验确认

要确认所有这一切,我们来看看文章开始时写的ReportFunction的输出。这个函数的目的是跟踪isa指针并记录。

要运行ReportFunction,我们需要创建一个实例,并调用report方法。

1
2
3
id instanceOfNewClass = [[newClass alloc] initWithDomain:@"someDomain" code:0 userInfo:nil];
[instanceOfNewClass performSelector:@selector(report)];
[instanceOfNewClass release];

既然没有声明report方法,那么就使用performSelector:调用它,所以编译器不会发出警告。

ReportFunction将遍历isa指针并打印对象的类,元类和类的元类。

获取对象的类ReportFunction使用object_getClass获取isa指针,是因为isa指针是类受保护成员(你不能直接访问对象的isa指针)。ReportFunction不使用class方法,因为调用Class对象上的class方法不返回元类,而是再次返回Class(所以[NSString class]将返回NSString类而不是NSString元类)。

以下是程序运行的输出(去掉了NSLog前缀):

1
2
3
4
5
6
7
8
This object is 0x10010c810.
Class is RuntimeErrorSubclass, and super is NSError.
Following the isa pointer 1 times gives 0x10010c600
Following the isa pointer 2 times gives 0x10010c630
Following the isa pointer 3 times gives 0x7fff71038480
Following the isa pointer 4 times gives 0x7fff71038480
NSObject's class is 0x7fff710384a8
NSObject's meta class is 0x7fff71038480

查看打印isa得到的地址:

  • 对象是地址0x10010c810
  • 该类是地址0x10010c600
  • 元类是地址0x10010c630
  • 元类的类(即NSObject元类)是地址0x7fff71038480
  • NSObject元类的类就是本身。

地址的值不是很重要,它只是显示了从类到元类再到NSObject元类的过程。

结论

元类是Class对象的类。每个Class都有自己独特的元类(因为每个Class都可以有自己独特的方法列表)。这意味着所有Class对象本身都不是同一个类。

元类能确保Class对象有所有底层类的实例和类方法,再加上所有自己的类方法。对于继承自NSObject的类,这意味着所有的Class(和元类)对象都定义了NSObject的所有实例和协议方法。

所有元类都使用基类的元类(NSObject的元类)作为它们的类,包括基元类,它是运行时中唯一的自循环定义类(它的元类指向自己)。


本文译自What is a meta-class in Objective-C?