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 看完了这篇文章 看看下图是不是豁然明朗
