译文 : 什么是Objective-C中的元类

原文地址 : What is a meta-class in Objective-C?

在这篇文章中,我将介绍Objective-C中的一个陌生概念 - 元类。 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]));
}

从表面上看,这一切都非常简单。在运行时创建类只需三个简单的步骤:

  1. 类对分配存储空间 (使用objc_allocateClassPair)
  2. 根据需要向类中添加方法和ivars (我使用class_addMethod添加了一个方法)
  3. 注册该类来使用它 (使用objc_registerClassPair)

然而,当前的问题是:什么是class pair?函数objc_allocateClassPair只返回一个值:class。那pair呢?

我确定你已经猜到了,另一半pair就是元类(meta-class)(这是这篇文章的标题)但是为了解释这是什么以及你为什么需要它,我将介绍一些关于Objective-C中的类和对象的知识点。

一个对象的数据结构中都需要(包含)什么

每个对象都有一个类(对象是类的实例)。这是面向对象的基本概念,但在Objective-C中,它也是数据的基本部分。任何具有指向正确位置(存在)的类的指针的数据结构都可以视为对象。

实际上,Objective-C中对象的基本定义如下所示:

typedef struct objc_object {
    Class isa;
} *id;
// ********* 最新的源码中 objc_object 数据结构发生了变化,这篇文章只做译文,不做其他深究 **********

这就是说:任何以指向类结构的指针开头的结构都可以被视为objc_object

Objective-C中对象最重要的特性是你可以向它们发送消息:

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

这能够正常运行时因为在向Objective-C对象(如此处的NSCFString)发送消息时, 运行时根据对象的isa指针来获取对象的类(上面的案例中是NSCFString类),这个类包含一个适用于所有该类的对象的方法列表和一个用来查找继承方法的指向父类的指针.运行时通过该类和其父类的方法列表来找到一个与消息选择器匹配的方法,然后运行时调用该方法的函数(IMP).

什么是元类(meta-class)?

现在,正如你理解的那样,Objective-C中的一个类也是一个对象。 这意味着你可以向类发送消息。

NSStringEncoding defaultStringEncoding = [NSString defaultStringEncoding];

上面的例子中,是向类NSString发送消息defaultStringEncoding.

上面的例子能够正常运行,是因为Objective-C中的每个类都是对象,这意味着类结构体必须以isa指针开头,才能复合上面提到的objc_object结构体的格式,并且结构体中下一个字段必须是指向父类的指针(或者已经是基类,该指针为空)

根据当前版本的runtime来说,不管以何种方式定义的类,都会以isa指针开头,后面紧跟父类指针

typedef struct objc_class *Class;
struct objc_class {
    Class isa;
    Class super_class;
    /* followed by runtime specific details... */
};

但是,为了能够调用类方法,类的isa指针本身必须指向一个类结构体,并且该类结构体必须包含一个我们可以用类来调用的方法列表

这引出了meta-class的定义 : 元类是类对象的类.

简单来说:

  1. 当向一个对象发送消息时,jiang'hui在这个对象的类的方法列表中查找
  2. 当向一个类发送消息时, 这条消息将会被在这个类的元类的方法列表中查找

元类是必不可少的, 因为它存储一个类的所有类方法,每个类都必须有一个唯一的元类,因为每个类都有一个唯一的类方法列表.

元类的类是什么?

元类和之前的类一样,也是一个对象,这意味着你也可以向元类发送消息.当然这也意味着元类也必须有一个类.

所有的元类共用基类的元类(继承层次结构中顶级类的元类)来作为它们的类,这说明对于所有继承自NSObject的类,它们的元类都以NSObject的元类为类.

所有的元类都遵循这么一条规则 : 基类的元类是它们的类, 任何基元类都是他们自己的类(即它们的isa指针指向它们自己). 这说明NSObject元类的isa指针指向它自己.

类和元类的继承

类的父类指针(super_class)指向它的父类,同样,元类自己的父类指针指向该元类的父类.

进一步来看,基类的父类指针也指向基类本身.

这个继承层级结构适用与所有实例,在这个继承层级结构中,类和元类都继承自该层级结构中的基类.

对于所有继承自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对象上的类方法不返回元类,而是再次返回该类(因此[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元类的类是它自己本身

上面地址的值是用来标识从对象->类->元类->NSObject元类的层级关系,值本身并没有意义

结论

元类是类对象的类. 每个类都有自己唯一的元类(因为每个类都有自己唯一的方法列表)。这说明所有类对象并不都是同一个类.

元类将始终确保类对象具有层次结构中基类的所有实例和类方法,以及中间的所有类方法。对于继承自NSObject的类,同样继承了所有NSObject实例方法和类方法.

所有元类使用基类的元类(NSObject元类用于所有继承自NSObject的类)作为它们的类,包括根元类,它是运行时中唯一的自定义类。

能力有限,有不对的地方欢迎指正.

你可能感兴趣的:(译文 : 什么是Objective-C中的元类)