(译文)什么是Objective-C中的元类(Meta-class)?

翻译自原文 Cocoa with love

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

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

  1. 为“类对”分配存储(使用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指针来获取对象的ClassNSCFString本例中的类)。该Class则包含Methods列表适用于所有对象 Class和指针指向superclass来查找继承的方法。运行时查看Classsuperclass中的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上调用一个方法,这个类Classisa指针本身必须指向一个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_getClassisa因为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 看完了这篇文章 看看下图是不是豁然明朗


(译文)什么是Objective-C中的元类(Meta-class)?_第1张图片
image

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