Objective-C 中的元类(meta class)

在本文咱们会看到一个在Objective-C中很陌生的概念——元类。Objective-C中的每一个类都有和本身相关联的元类,但咱们几乎历来不直接使用它,它们依然是那么神秘。咱们将开始学习怎样在运行时建立一个类。经过建立的“class pair”,我会解释什么是元类,而后探讨它对于Objective-C中对象和类的意义。html

在运行时建立一个类

下面的代码在运行时建立了一个NSError的子类,而且添加了一个方法:objective-c

Objective-C数据结构

 

1函数

2学习

3atom

4spa

Class newClass =指针

    objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", 0);code

class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:");orm

objc_registerClassPair(newClass);

ReportFunction函数就是添加的实例方法,具体实现以下

Objective-C

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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]));

}

表面上看来,这至关简单。在运行时建立一个类只须要3个步骤:o

  1. 为”class pair”分配内存 (使用objc_allocateClassPair).
  2. 添加方法或成员变量到有须要的类里 (我已经使用class_addMethod添加了一个方法).
  3. 注册类以便它能使用 (使用objc_registerClassPair).

然而,有一个很迫切的问题:什么是“class pair”?objc_allocateClassPair函数仅返回了一个值:the class。那另外一半pair在哪?

我相信你已经猜到了,另外一半pair就是元类(这篇文章的主题)。为了解释它是什么和咱们为何须要它,还须要交代下Objective-C的对象和类的相关背景。

什么数据结构才能称之为对象?

每一个对象都有类。这是面向对象的基本概念,可是在Objective-C中,它对数据结构也同样。含有一个指针且该指针能够正确指向类的数据结构,均可以被视做为对象。

在Objective-C中,对象的类是isa指针决定的。isa指针指向对象所属的类。

实际上,Objective-C中对象最基本的定义是这样的:

Objective-C

 

1

2

3

typedef struct objc_object {

    Class isa;

} *id;

这说的是:任何带有以指针开始并指向类结构的结构均可以被视做objc_object。

Objective-C中对象最重要的特色是你能够发送消息给它们:

Objective-C

 

1

2

[@"stringValue"

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

这能工做是由于Objective-C对象(这儿是NSCFString)在发送消息时,运行时库会追寻着对象的isa指针获得了对象所属的类(这儿是NSCFString类)。这个类包含了能应用于这个类的全部实例方法和指向超类的指针以即可以找到父类的实例方法。运行时库检查这个类和其超类的方法列表,找到一个匹配这条消息的方法(在上面的代码里,是NSString类的writeToFile:atomically:encoding:error方法)。运行时库基于那个方法调用函数(IMP)。

重点就是类要定义这个你发送给对象的消息。

什么是元类

如今,可能你已经知道了,Objective-C的一个类也是一个对象。这意味着你能够发送消息给一个类。

Objective-C

 

1

NSStringEncoding defaultStringEncoding = [NSString defaultStringEncoding];

在这个示例里,defaultStringEncoding被发送给了NSString类。

由于Objective-C中每一个类自己也是一个对象。如上面所展现的,这意味着类结构必须以一个isa指针开始,从而能够和objc_object在二进制层面兼容,而后这个结构的下一字段必须是一个指向超类的指针(对于基类则为nil)。

正如我上周展现的,类被定义的方式有点不一样,依赖于你的运行时库版本,可是,它们都以isa字段开始,随后是superclass字段。

Objective-C

 

1

2

3

4

5

6

typedef struct objc_class *Class;

struct objc_class {

    Class isa;

    Class super_class;

    /* followed by runtime specific details... */

};

为了调用类里的方法,类的isa指针必须指向包含这些类方法的类结构体。

这就引出了元类的定义:元类是类对象的类。
简单说就是:

  • 当你给对象发送消息时,消息是在寻找这个对象的类的方法列表。
  • 当你给类发消息时,消息是在寻找这个类的元类的方法列表。

元类是必不可少的,由于它存储了类的类方法。每一个类都必须有独一无二的元类,由于每一个类都有独一无二的类方法。

元类的类是什么?

元类,就像以前的类同样,它也是一个对象。你也能够调用它的方法。天然的,这就意味着他必须也有一个类。

全部的元类都使用根元类(继承体系中处于顶端的类的元类)做为他们的类。这就意味着全部NSObject的子类(大多数类)的元类都会以NSObject的元类做为他们的类

根据这个规则,全部的元类使用根元类做为他们的类,根元类的元类则就是它本身。也就是说基类的元类的isa指针指向他本身。

类和元类的继承

类用 super_class指针指向了超类,一样的,元类用super_class指向类的super_class的元类。

说的更拗口一点就是,根元类把它本身的基类设置成了super_class。

在这样的继承体系下,全部实例、类以及元类(meta class)都继承自一个基类。

这意味着对于继承于NSObject的全部实例、类和元类,他们可使用NSObject的全部实例方法,类和元类可使用NSObject的全部类方法

这些文字看起来莫名其妙难以理解。Greg Parker给出了一份精彩的图谱来展现这些关系:

实验证实

为了验证,让咱们看看我在文章开始写的ReportFunction 函数的输出。这个函数的目的是跟随isa指针并打印出它的路途。

为了运行ReportFunction,咱们须要建立一个动态实例来建立类调用report方法。

Objective-C

 

1

2

3

4

id instanceOfNewClass =

    [[newClass alloc] initWithDomain:@"someDomain" code:0 userInfo:nil];

[instanceOfNewClass performSelector:@selector(report)];

[instanceOfNewClass release];

这里没有声明report方法,但我使用performSelector:调用它,因此编译器不会给出警告。
而后ReportFunction函数会沿着isa进行检索,来告诉咱们class,meta-class以及meta-class的class是什么样的状况:

获得对象的类:ReportFunction 函数使用object_getClass跟踪isa指针,由于isa指针是类的保护成员(你不能直接接收其余对象的isa指针)。ReportFunction不使用类方法,由于在类对象里调用类方法不能返回元类,它会再次返回这个类(所以[NSString class]会返回NSString 类而不是NSString元类)

This is the output (minus NSLog prefixes) when the program runs:
这是程序运行时的输出(省略了NSlog前缀):

Objective-C

 

1

2

3

4

5

6

7

8

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)都有本身独一无二的元类(每一个类都有本身第一无二的方法列表)。这意味着全部的类对象都不一样。

元类老是会确保类对象和基类的全部实例和类方法。对于从NSObject继承下来的类,这意味着全部的NSObject实例和protocol方法在全部的类(和meta-class)中均可以使用。

全部的meta-class使用基类的meta-class做为本身的基类,对于顶层基类的meta-class也是同样,只是它指向本身而已。

相关文章
相关标签/搜索