Objective-C 中的 Meta-class 是什么?
2013-12-11 10:14 三戒1993 阅读(157) 评论(0) 编辑 收藏 举报
在这篇文章中,我关注的是 Objective-C 中的一个陌生的概念—— meta-class。在 Objective-C 中的每个类都有一个相关联的 meta-class,但是你很少会直接使用 meta-class,他们仍旧保持着神秘的面纱。我们
转自6david9 的博客
在这篇文章中,我关注的是 Objective-C 中的一个陌生的概念—— meta-class。在 Objective-C 中的每个类都有一个相关联的 meta-class,但是你很少会直接使用 meta-class,他们仍旧保持着神秘的面纱。我们从在运行时创建一个类开始。通过查看 “class pair”,我会解释 meta-class 是什么,同时也会谈谈在 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]));
- }
表面上来看,非常简单。在运行时创建一个类只需要这三步:
1、为 “class pair” 创建存储空间(使用 objc_allocateClassPair)。
2、为这个类添加所需的 methods 和 ivars(我已经使用 class_addMethod 添加过一个方法了)。
3、注册这个类,然后就可以使用了(使用 objc_registerClassPair)。
然后,中级问题是:“class pair” 是什么?函数 objc_allocateClassPair 只返回了一个值:这个 class。这一对中的另一个在哪?(译注:pair 有 “一对,一双” 的意思)
我敢肯定你已经猜到了另一半就是 meta-class(就是这篇文章的标题),但是要解释那是什么和你为什么需要它,我需要介绍一些在 Objective-C 中的关于对象和类的背景知识。
把一个数据结构变为对象需要什么?
每个对象都有一个类。这是面相对象概念的基础知识,但在 Objective-C 中不是这样,它(译注:class)同样是这个数据的一部分。每个可以被当成对象的数据结构都在恰当的位置有一个指向一个类的指针。
在 Objective-C,一个对象的类由它的 isa 指针决定。isa 指针指向这个对象的 Class。
事实上,在 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)发送消息的时候,runtime 沿着对象的 isa 指针找到了这个对象的 Class(这里是 NSCFString 的类)结构体。 Class 结构体中包含了一个这个类的方法列表和一个指向父类的指针,用于查找继承的方法。
关键点是 Class 结构体中定义了你可以向一个对象发送的消息。
meta-class 是什么?
现在,你可能已经知道,在 Objective-C 中一个 Class 也是一个对象。这就意味着你也可以向一个 Class 发送消息。
- NSStringEncoding defaultStringEncoding = [NSString defaultStringEncoding];
这里,向 NSString 类发送了 defaultStringEncoding。
可以这么做是因为在 Objective-C 中每个 Class 它自己同样也是个对象。也就是说 Class 结构体必须以 isa 指针开始,然后就可以在二进制兼容(binary compatible)我上面介绍的 objc_object 结构了,接着下一个字段必须是一个指向它的父类的指针(要是类就是基类就是 nil)。
我上周已经介绍过,定义一个类有好几种方法,主要依赖于你正在运行的 runtime 的版本。但,是的,都是由一个 isa 字段开始然后是 superclass 字段。
- typedef struct objc_class *Class;
- struct objc_class {
- Class isa;
- Class super_class;
- /* 以下依赖于 runtime 的具体实现 …… */
- };
然而,为了让我们在 Class 上调用一个方法,Class 的 isa 指针必须指向一个 Class 结构体,并且那个 Class 结构体必须包含我们可以在那个 Class 上调用的方法的列表。
这就引出了 meta-class 的定义:meta-class 是 Class 对象的类(the meta-class is the class for a Class object)。
简单来说:
1.当你向一个对象发送消息,就在那个对象的方法列表中查找那个消息。
2.当你想一个类发送消息,就再那个类的 meta-class 中查找那个消息。
meta-class 是必须的,因为它为一个 Class 存储类方法。每个类都必须有一个唯一的 meta-class,因为每个 Class 都有一个可能不一样的类方法。
meta-class 的类是什么?
meta-class,如之前的 Class,同样是个对象。这就意味着你也可以在它上面调用方法。自然的,这就意味着它也必须有一个类(译注:isa 指针)。
所有的 meta-class 使用它们基类的 meta-class (继承层次中最顶层的 Class 的 meta-class)作为它们自己的类。这就是说所有继承自 NSObject 的类(大部分的类),以 NSObject 的 meta-class 作为自己的 meta-class 的类。
遵循这个规则,所有的 meta-class 使用基类的 meta-class 作为他们的类,任何基类的 meta-class 将会是他们自己(它们的 isa 指向他们自己)。这就是说 NSObject 的 meta-class 的 isa 指针指向它们自己(是自己的一个实例)。
class 和 meta-class 的继承
和 Class 以 super_class 指针指向它的父类的方法一样,meta-class 以 super_class 指针指向 Class 的 super_class 的 meta-class。(译注:这句话有点绕,就是 super-class 一个指向 Class 的父类,一个指向 meta-class 的父类。Class 是一般对象的类型,meta-class 是 Class 的类型。)
进一步来讲,基类的 meta-class 设置 super_class 指针指向基类自己。
这个继承层次的结果就是,所有在这个继承层次中的的实例,类和 meta-class 都继承了基类的层次。
对于所有在 NSObject 层次中的实例,类和 meta-class,这就意味着所有 NSObject 的实例方法都是有效的。对于类和 meta-class,所有 NSObject 的类方法也同样是有效的。
所有这些在字面上相当让人困惑。Greg Parker 已经把实例,类,meta-class 还有他们的超类以非常棒的图解的方式聚合在一起,展示他们是如何在一起工作的。
用实验验证这点
为了验证这些,让我们看看在我文章开头提供的 ReportFunction 的输出。这个函数的目的是顺着 isa 指针打引出它找到的。
要运行 ReportFunction,我们需要为这个动态创建的类创建一个实例,然后在上面调用这个方法。
- id instanceOfNewClass =
- [[newClass alloc] initWithDomain:@"someDomain" code:0 userInfo:nil];
- [instanceOfNewClass performSelector:@selector(report)];
- [instanceOfNewClass release];
因为没有这个方法的声明,所以我使用 performSelector: 调用这个方法,这样编译器就不会输出警告了。
现在 ReportFunction 会沿着 isa 指针告诉我们这个对象使用了哪些类,meta-class 和 meta-class 的类。
获取一个对象的类:ReportFunction 使用 object_getClass 跟随 isa 指针,因为 isa 指针是一个类中一个受保护的成员变量(你不能直接访问其他对象的 isa 指针)。ReportFunction 没有以类方法的形式这样调用,因为在 Class 对象上调用类方法不会返回 meta-class,而是再次返回 Class 对象(所以 [NSString class] 会返回 NSString 的类而不是 NSString
的 meta-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
看看通过递归的查看 isa 的地址:
the object is address 0x10010c810.
the class is address 0x10010c600.
the meta-class is address 0x10010c630.
the meta-class’s class (i.e. the NSObject meta-class) is address 0x7fff71038480.
the NSObject meta-class’ class is itself.
地址的值不是很重要,只是演示了上面讨论的从类到 meta-class 到 NSObject 的 meta-class 的过程。
结论
meta-class 是 Class 对象的类。每个 Class 都有个不同的自己的 meta-class(因此每个 Class 都可以有一个自己不同的方法列表)。也就是说每个类的 Class 不完全相同。
meta-class 总是会保证 Class 对象会有从基类继承的所有的的实例和类方法,加上之后继承的类方法。如从 NSObject 继承的类,就意味着在所有的 Class(和 meta-class)对象中定义了所有从 NSObject 继承的实例和协议方法。
所有的 meta-class 使用基类的 meta-class(NSObject 的 meta-class 用于继承自 NSObject 的类)作为他们自己的类,包括在运行时自己定义的基础的 meta-class。