美文网首页
(译文)什么是Objective-C中的元类(Meta-clas

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

作者: Bc_wh1te_Le1 | 来源:发表于2018-06-15 10:48 被阅读20次

    翻译自原文 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

    相关文章

      网友评论

          本文标题:(译文)什么是Objective-C中的元类(Meta-clas

          本文链接:https://www.haomeiwen.com/subject/rqrheftx.html