(译文)什么是Objective-C中的元类(Meta-clas
please note 如文开头所说 文章由于时间久远 可能会有代码过时的风险 但本文只是理解原理 不用在意
译文:
在这篇文章中,我将审视(look at)Objective-C中的一个陌生的概念 - 元类(the meta-class)。Objective-C中的每个类都有自己的关联元类,但由于您很少直接使用元类,所以它们仍旧保持神秘。我将首先看看如何在运行时创建一个类。通过检查这个创建的“类对”(class pair),我将解释元类是什么,并且还涵盖了数据在Objective-C中是对象还是类的含义。
在运行时创建一个类
以下代码将在运行时创建一个新的NSError
子类并为其添加一个方法:
Class newClass = objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", 0);
class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:");
objc_registerClassPair(newClass);
添加的方法使用名为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]));
}
从表面上看,这非常简单。在运行时创建一个类只需三个简单的步骤:
- 为“类对”分配存储(使用
objc_allocateClassPair
)。
2.根据需要将方法和ivars添加到类中(我已经用class_addMethod
添加了一个方法)
3.注册该类以便可以使用(使用objc_registerClassPair
)。
然而,直接的问题是:什么是“类对(class pair)”?该函数objc_allocateClassPair
只返回一个值:类。另一半在哪里?
我相信你已经猜到了这一对的另一半是元类(meta class)(这是这篇文章的标题),但要解释它是什么以及为什么你需要它,我将给出一些关于对象和类的背景知识在Objective-C中。
数据结构成为一个对象需要什么?
每个对象都有一个类。这是一个基本的面向对象的概念,但在Objective-C中,它也是数据的基础部分。任何具有指向正确位置的类的指针的数据结构都可以视为一个对象。
在Objective-C中,对象的类由其isa
指针决定。该isa
指针指向对象的类。
实际上,Objective-C中对象的基本定义如下所示:
typedef struct objc_object {
Class isa;
} *id;
这就是说:任何以指向Class结构的指针开始的结构都可以视为一个objc_object
。
Objective-C中对象的最重要特性是可以向它们发送消息:
[@"stringValue" writeToFile:@"/file.txt" atomically:YES encoding:NSUTF8StringEncoding error:NULL];
这是有效的,因为当你向Objective-C对象发送消息时(比如NSCFString
这里),运行时会跟随对象的isa
指针来获取对象的Class
(NSCFString
本例中的类)。该Class
则包含Methods
列表适用于所有对象 Class
和指针指向superclass
来查找继承的方法。运行时查看Class
和superclass
中的Methods
列表以找到与消息选择器相匹配的一个(在上面的例子中,writeToFile:atomically:encoding:error
on NSString
)。运行时然后调用该方法function(IMP
)。
重要的一点是Class
定义可以发送给对象(instance)的消息。
什么是元类?
现在,您可能已经知道,类Class
在Objective-C中也是一个对象。这意味着你可以发送消息给一个类Class
。
NSStringEncoding defaultStringEncoding = [NSString defaultStringEncoding];
在这种情况下,defaultStringEncoding
发送给NSString
类。
这是有效的,因为每一个类Class
在Objective-C中都是一个对象本身。这意味着Class
结构必须以一个isa
指针开始,以便它与objc_object
上面显示的结构二进制兼容,并且结构中的下一个字段必须是指向superclass
(或nil
基类)的指针。
正如我上周展示的Class
,根据您运行的运行时版本,有几种不同方式的定义 ,但是可以确定的是,它们都以isa
字段开头,后跟superclass
字段。
typedef struct objc_class *Class;
struct objc_class {
Class isa;
Class super_class;
/* followed by runtime specific details... */
};
然而,为了让我们在类Class
上调用一个方法,这个类Class
的isa
指针本身必须指向一个Class
结构,并且该Class
结构必须包含Methods
列表,这样我们可以在该类上调用想用的方法。
这引出了元类的定义:元类(meta-class)是Class
对象的类。
简单的说:
- 当你向一个对象(实例)发送消息时,该消息将在对象所属类(object's class)的方法列表中查找。
- 当你向一个类发送一条消息时,该消息将在类的元类(class' meta-class)的方法列表中查找。
元类是必不可少的,因为它存储一个类的类方法。每一个类Class
必须有一个独一无二的元类,因为每个类Class
都有一个潜在的唯一的类方法列表。
元类的类是什么?
元类与Class
之前一样,也是一个对象。这意味着你也可以调用它的方法。当然,这意味着它也必须有一个类Class
。
所有元类都使用基类的元类(Class
继承层次结构中顶层的元类)作为它们的类。这意味着对于所有从NSObject
(大多数类)中继承下来的类,元类使用NSObject
元类作为它的类。
遵循所有元类使用基类的元类作为它们的类的规则,任何基类元类都将是它自己的类(它们的isa
指针指向它们自己)。这意味着元类isa
上的指针指向NSObject它自己(它是它自己的一个实例)。
类和元类的继承
以同样的方式,Class
指向它的父类super_class
的指针,元类指向元类的 super_class
利用自身的super_class
指针。
为了解决更进一步的奇怪问题(As a further quirk 字面意思作为一个进一步的怪癖),基类的元类将其super_class
设置为基类本身。
这个继承层次的结果是层次结构中的所有实例、类和元类都继承了层次结构的基类。
对于NSObject
层次结构中的所有实例,类和元类,所有NSObject
实例方法都是有效的。对于类和元类,所有的NSObject
类方法也是有效的。
所有这些概念在文本中有些混乱。Greg Parker汇集了一个关于实例,类,元类和他们的超类以及它们如何组合在一起的优秀图表。
实验证实
为了确认所有这些,让我们看看在ReportFunction
这篇文章开始时我给出的输出结果。这个函数的目的是跟随isa
指针并记录它找到的内容。
为了运行ReportFunction,我们需要创建一个动态创建的类的实例并调用它的report方法。
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程序运行时的输出(减前缀):
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元类的类本身。
地址的价值并不重要,只是它展示了从类到meta-class到NSObject meta-class 的进展。
结论
元类是Class
对象的类。每个Class
都有自己独特的元类(所以每个Class
都可以拥有自己独特的方法列表)。这意味着所有的Class
对象都不是同一个类。
元类将始终确保该Class
对象具有层次结构中基类的所有实例和类方法,以及中间的所有类方法。对于后继类NSObject
,这意味着所有NSObject实例和协议方法都是为所有Class(和元类)对象定义的。
所有元类本身都使用基类的元类(NSObject
元类作为NSObject的继承类)作为它们的类,包括基本级元类,它是运行时中唯一的自定义类(self-defining class)。
PS 译者拓展
so 看完了这篇文章 看看下图是不是豁然明朗
