(译文)什么是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 看完了这篇文章 看看下图是不是豁然明朗


image
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,961评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,444评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,009评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,082评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,101评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,271评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,738评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,395评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,539评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,434评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,481评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,160评论 3 317
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,749评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,816评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,038评论 1 256
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,548评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,140评论 2 341

推荐阅读更多精彩内容