Objectivce-C中的meta-class是什么

3/20/2017 iOSRuntime

Copyright 2010 Matt Gallagher: cocoawithlove.com (opens new window).

原文: What is a meta-class in Objective-C? (opens new window). 本文由原作者授权翻译

在这篇文章中,我着重讲解Objective-C中的一个陌生的概念 - meta-class。Objective-C中的每个类都有一个与之关联的meta-class,但因为你很少直接使用它,它才会对你显得如此神秘。我将从如何使用runtime机制创建一个类说起。通过检查 objc_allocateClassPair 函数创建出的 "class pair" ,我将解释什么是meta-class,也会解释一些普遍存在的问题:在Objective-C中,meta-class 对一个对象或者一个类有着怎样的意义?

# 在程序运行的时候创建一个类

在程序运行的时候,下面的代码创建了一个NSError的子类,并给它添加了一个方法。

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

这个被添加的方法使用ReportFunction函数作为它的实现。ReportFunction函数的定义如下:

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
2
3
4
5
6
7
8
9
10
11
12
13

表面上来看,这一切都很简单。

因为在程序运行的时候创建一个类只需要这三个简单的步骤:

  1. class pair开辟内存空间(使用objc_allocateClassPair).
  2. 按照函数所需要,去给类添加方法和成员变量(我已经使用class_addMethod添加了一个方法)
  3. 注册这个类,以便能够使用它(使用 objc_registerClassPair).

但是,现在的问题是:什么是class pair? objc_allocateClassPair函数只返回了一个值: 类。那这个class pair的另一半又在哪呢?我相信你已经猜到了:class pair的另一半就是meta-class(它是这篇文章的标题),但我需要向你解释它是什么,为什么你需要它,我将会给出一些Objectivce-C中的类和对象的背景。

# 让一个数据结构变成一个对象,需要些什么?

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

在Objectivce-C中,一个对象的类被一个isa指针所决定。这个isa指针指向对象的类。

实际上,在Objectivce-C中,一个对象的基础定义是这样的:

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

👆这个定义说明:任何一个 以指向一个Class结构体的指针 开始的结构体都能够被视为一个对象。

在Objectivce-C中,对象最重要的功能就是我们能够给它们发送消息:

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

这段代码之所以会执行,是因为当你向一个OC对象发送一个消息的时候(像这里的NSCFString),runtime机制会沿着对象的isa指针去获取对象的Class(在这里是NSCFString类)。接着,这个Class包含了一个适用于该类的所有对象的方法列表(拥有所有对象方法的列表)和一个指向超类的指针(用于查阅继承的方法)。由于获取到了对象对应的Class,这时,运行时机制会为了找到一个和消息选择器匹配的方法,浏览在Classsuperclass上的方法列表(在上述情况下,writeToFile:atomically:encoding:error是在NSString的方法列表上的)。接着,runtime机制会执行这个方法对应的实现函数(IMP)。

重要的一点是: Class定义了你能够发送给对象的消息(对象方法列表)。

# 什么是meta-Class?

现在,像你所知道的: 一个Class在Objectivce-C中也是一个对象。这个就意味着你也能够给一个Class发送消息。

NSStringEncoding defaultStringEncoding = [NSString defaultStringEncoding];
1

在这里,defaultStringEncoding被发送给了NSString类.

这段代码之所以会执行,是因为在Objectivce-C中每一个Class其本质上也是一个对象.这就意味着Class结构体必须是以一个isa指针开始的结构体,以至于它与我上面显示的objc_object结构体是二进制兼容的,并且下一个在结构体中的字段必须是一个指向superclass的指针(或者对于基本类来说就是nil).

像我上周展示的一样 (opens new window),这里有几种不同定义Class的方式,这取决于你所运行的runtime的版本,但是,它们都是以一个isa字段开头,后跟一个superclass字段。

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

但是,为了让我们能在Class上执行一个方法,Classisa指针必须指向一个Class结构体, 并且这个Class结构体必须包含了能够让我们在类上执行方法的方法列表(类方法列表).

这就引出了meta-class的定义: meta-class是一个Class对象的类.

简而言之:

  • 当你给一个对象发送消息的时候,runtime机制会在对象的类对象的方法列表中查找该消息.
  • 当你给一个发送消息的时候,runtime机制会在类对象的meta-class的方法列表中查找该消息.

meta-class是必须存在的,因为它为一个Class保存了该类的类方法。 对于每一个Class来说,都必须有一个独一无二的meta-class,因为每一个Class都有一个可能独一无二的类方法列表.

#meta-class的类的是什么呢?

meta-class像之前的Class一样,它也是一个对象.这就意味着你同样能够在它之上执行方法。那它也理所当然的必须属于一个类(isa指针)。

所有的meta-class使用基础类的meta-class(在它们继承体系的顶层的类的meta-class)作为它们的类。这就意味着所有继承自NSObject的类的meta-classClass(isa指针)是NSObjectmeta-class.

遵循这样的规则:所有的meta-class都使用基本类的meta-class作为它们的类(isa指针),任何基本(顶层)的meta-classClass(isa指针)都将是它们自身(它们的isa始终指向自身).这就意味着NSObjectmeta-classisa指针是指向它自身的(它是它自身的实例).

# meta-classClass的继承

相同的是Class使用super_class指针指向其父类Class,meta-class使用自身的super_class指针指向Classsuper_classmeta-class。(此处meta-class->super_class = class->super_class->meta-class)

还有个奇葩就是,基类的meta-class(isa)的super_class指向的是基类本身.

这样的继承体系导致的结果就是所有的实例,类和meta-class都继承自基类.

对于所有在NSObject体系下的实例,类和meta-class的来说,NSObject的所有的对象方法对它们来说都是有效的。对于类和meta-class来说,所有的NSObject的类方法是有效的。

# 通过实验证明以上观点

为了证明以上观点,让我们看看我在文章开头给出的ReportFunction函数的输出吧.这个函数的目的是沿着isa指针并且打印它找到的是什么.

为了执行ReportFunction函数,我们需要创建一个动态创建的类的实例并且执行report对象方法.

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

因为这里并没有声明report方法,所以我为了编译器不会给出一个警告,使用performSelector:去执行它.

现在ReportFunction将会遍历所有的isa指针并且告诉我们什么对象被用作Class,meta-Classmeta-Class的类

获取一个对象的类: ReportFunction使用object_getClass去得到isa指针,应为isa是类的保护成员(你不能直接的访问其他对象的isa指针).ReportFunction没有使用class方法的原因是: 在一个类对象上执行class方法不会返回meta-Class,它始终都只会返回Class(所以[NSString class]会返回NSString类而不是NSStringmeta-class`).

这是NSObject的class对象方法和类方法的实现:

当程序运行的时候,这是它的输出(去掉了NSlog的前缀):

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
1
2
3
4
5
6
7
8

看着下面通过反复沿着isa指针到达的内存地址:

  • 对象的地址是0x10010c810
  • 类的地址是0x10010c600
  • meta-class的地址为0x10010c630
  • meta-classClass(即NSObjectmeta-class)的地址为0x7fff71038480
  • NSObjectmeta-classClass的地址是它本身的地址

地址的值除了显示了我们之前讨论的从类到meta-class再到NSObjectmeta-class的进展以外,其他的都并不重要.

# 结论

meta-class是一个Class对象的类.每一个Class都有一个自己的独特meta-class(因为每个Class有着自己的独特的方法列表).这就意味着所有的Class对象都各不相同.

meta-class总是会确保Class对象拥有在层级的顶层的基类(NSObject)的对象方法和类方法,再加上基类到当前类之前的类的类方法。对于继承自NSObject的类来说,NSObject的对象和协议方法是定义给所有的Class(和meta-class)对象.

✍️ :在层级顶层的NSObject的meta-classsuperclass指针是指向NSObject的类对象的,所以继承自NSObject的类的meta-class中会包含有NSObject所有的对象方法和类方法。

原文:The meta-class will always ensure that the Class object has all the instance and class methods of the base class in the hierarchy, plus all of the class methods in-between. For classes descended from NSObject, this means that all the NSObject instance and protocol methods are defined for all Class (and meta-class) objects.

所有的meta-class都使用基类的meta-class(在NSObject继承体系下,是NSObjectmeta-class)作为他们的类,也包括了在运行时机制中唯一自定义的基础级的meta-class.


补充:

iOS类型编码 (opens new window)

id Class meta-class 之间的关系

Last Updated: 7/20/2021, 6:00:39 PM