原文地址: http://www.cocoawithlove.com/2010/01/what-is-meta-class-in-objective-c.html
在这篇文章中,我看到了Objective-C中的一个陌生概念——元类。Objective-C中的每个类都有它自己的相关元类,但是由于您很少直接使用元类,所以它们仍然是谜一样的。我将从如何在运行时创建类开始。通过检查这个创建的“类对”,我将解释元类是什么,并且还涵盖了Objective-C中常见的类与数据。
用runtime创建一个类
下面的代码用运行时创建了NSError的一个新的子类,并添加了一个方法:
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]));
}
复制代码
表面上看,这一切都很简单。在运行时创建类仅仅是三个简单的步骤:
为“class pair”分配存储(使用objc_allocateClassPair)。 根据需要向类添加方法和ivars(我已经添加了一个使用class_addMethod的方法)。 注册类以便它可以被使用(使用objc_registerClassPair)。 然而,最直接的问题是:什么是“class pair”? 函数objc_allocateClassPair只返回一个值:类。另一半在哪里?
我确信你已经猜到了,另一半是元类(这是这个帖子的标题),但是为了解释这是什么以及为什么需要它,我将介绍Objective-C中的对象和类的一些背景知识。
数据结构需要什么才能成为对象?
每个对象都有一个类。这是一个基本的面向对象概念,但在Objective-C中,它也是数据的基础部分。任何具有指向正确位置上的类的指针的数据结构都可以被视为对象。
在Objective-C中,对象的类由它的isa指针决定。isa指针指向对象的类。
实际上,Objective-C中对象的基本定义是这样的:
typedef struct objc_object {
Class isa;
} *id;
复制代码
这就是说:任何从指向类结构的指针开始的结构都可以被视为objc_object。
Objective-C中对象最重要的特性是可以向它们发送消息:
[@"stringValue"
writeToFile:@"/file.txt" atomically:YES encoding:NSUTF8StringEncoding error:NULL];
复制代码
这是有效的,因为当您向Objective-C对象(比如这里的NSCFString)发送消息时,运行时将寻找对象的isa指针到达对象的类(在本例中为NSCFString类)。然后,该类包含一个方法列表,该列表适用于该类的所有对象,以及一个指向超类的指针,以查找继承的方法。运行时通过的关于类和超类的方法来找到一个匹配的消息选择器(在本例中,writeToFile:atomically:encoding:error on NSString)。然后运行时调用该方法的函数(IMP)。
重要的一点是,类定义了可以发送到对象的消息。
什么是meta-class?
你们可能已经知道,Objective-C中的一个类也是一个对象。这意味着您可以向类发送消息。
NSStringEncoding defaultStringEncoding = [NSString defaultStringEncoding];
复制代码
在这种情况下,defaultStringEncoding被发送到NSString类。
这是因为Objective-C中的每个类都是一个对象。这意味着类结构必须从一个isa指针开始,这样它就可以与我上面显示的objc_object结构相兼容,并且结构中的下一个字段必须是一个指向超类的指针(或基类的nil)。
有几种不同的方法可以定义一个类,这取决于您运行的运行时的版本,但是,它们都是从一个isa字段开始,然后是一个超类字段。
typedef struct objc_class *Class;
struct objc_class {
Class isa;
Class super_class;
/* followed by runtime specific details... */
};
复制代码
然而,为了让我们在类上调用方法,类的isa指针必须指向类结构,类结构必须包含我们可以在类上调用的方法的列表。
这导出了元类的定义:元类是类对象的类。
简单地说:
当向对象发送消息时,该消息将在对象类的方法列表中查找。 当您向一个类发送消息时,该消息将在类的元类的方法列表中查找。 元类非常重要,因为它存储类的类方法。每个类都必须有一个唯一的元类,因为每个类都有一个可能唯一的类方法列表。
元类的类是什么
元类,就像前面的类一样,也是一个对象。这意味着您也可以在它上调用方法。当然,这意味着它也必须有一个类。
所有元类都使用基类的元类(继承层次上的顶级类的元类)作为类。这意味着对于从NSObject(大多数类)层级的所有类,元类都是NSObject元类作为它的类。
遵循所有元类都使用基类的元类作为类的规则,任何基础元类都将是它自己的类(它们的isa指针指向它们自己)。这意味着在NSObject元类上的isa指针指向它自己(它本身就是一个实例)。
在此附上一张图帮助理解
类和元类的继承
就像类用它的超类指针指向超类一样,元类用它自己的超类指针指向类的超类的元类。
基类的元类将它的super_class设置为基类本身。
这种继承体系就是,体系中的所有实例、类和元类都继承了体系的基类。
对于NSObject层级中的所有实例、类和元类,这意味着所有NSObject实例方法都是有效的。对于类和元类,所有NSObject类方法都是有效的。
实验证实这一猜想
为了确认所有这些,让我们看一下我在本文开头给出的ReportFunction的输出。这个函数的目的是跟踪isa指针并记录它找到的内容。
要运行ReportFunction,我们需要动态创建类的实例并在其上转发方法。
id instanceOfNewClass =
[[newClass alloc] initWithDomain:@"someDomain" code:0 userInfo:nil];
[instanceOfNewClass performSelector:@selector(report)];
[instanceOfNewClass release];
复制代码
由于没有报告方法的声明,所以我使用performSelector来调用它:所以编译器不会发出警告。
ReportFunction现在将遍历isa指针并告诉我们什么对象被用作类、元类和元类的类。
获取一个对象的类:ReportFunction使用object_getClass来跟踪isa指针,因为isa指针是类的受保护成员(您不能直接访问其他对象的isa指针)。ReportFunction不使用类方法来执行此操作,因为在类对象上调用类方法不会返回元类,而是返回类(因此[NSString类]将返回NSString类而不是NSString元类)。
当程序运行之后,以下是输出
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元类的。
结论
元类是一个类的类对象。每个类都有自己的独特的元类(因为每一个类可以有它自己的独特的方法列表)。(This means that all Class objects are not themselves all of the same clas不会。。欢迎在讨论区补充)
类对象的元类总是确保所有基类的实例和类方法的体系,加上中间所有的类方法。NSObject的子类,这个子类含有所有NSObject实例和协议方法,这些是从元类被定义好了的。
所有的元类本身,使用基类的元类( NSObject元类)作为他们的类,包括基础的元类在运行时中都是唯一类。