Objective - c Runtime编程指南

作者: 乂滥好人 | 来源:发表于2018-07-26 15:54 被阅读21次

    前言

    此文档为官方Runtime文档的翻译,主要是为了方便学习,很多地方翻译有误,如有需要请选择性阅读。



    介绍

    重要提示:此文档不再更新。有关苹果SDKs的最新信息,请访问文档网站

    Objective-C语言从编译时间和链接时间到运行时推迟了尽可能多的决策。只要有可能,它就会动态地完成任务。这意味着该语言不仅需要编译器,还需要运行时系统来执行编译代码。运行时系统充当Objective-C语言的一种操作系统;这就是语言运作的原因。

    本文档介绍了NSObject类以及Objective-C程序如何与运行时系统交互。特别是,它检查了在运行时动态加载新类并将消息转发给其他对象的范例。它还提供有关在程序运行时如何查找有关对象的信息。

    您应该阅读本文档以了解Objective-C运行时系统的工作原理以及如何利用它。但是,通常情况下,您应该没有理由需要了解和理解这些材料来编写Cocoa应用程序。

    目录文件

    本文档包含以下章节:

    • 一、runtime版本和平台
    • 二、与runtime交互
    • 三、消息
    • 四、动态方法解析
    • 五、消息转发
    • 六、类型编码
    • 七、属性声明

    参考

    Objective-C运行时参考描述了Objective-C运行时支持库的数据结构和功能。 您的程序可以使用这些接口与Objective-C运行时系统进行交互。 例如,您可以添加类或方法,或获取已加载类的所有类定义的列表。

    使用Objective-C编程描述了Objective-C语言。

    Objective-C发行说明描述了最新版OS X中Objective-C运行时的一些更改。

    一、runtime版本和平台

    不同平台上有不同版本的Objective-C runtime

    1、'Legacy '和 'Modern' 两个版本

    Objective-C运行时有两个版本 “过去”和“现在”。 Modern版本随Objective-C 2.0一起推出,包含许多新功能。 Objective-C 1.0 参考中描述了Legacy版本的运行时的编程接口; Objective-C运行时参考中描述了Modern版本的运行时的编程接口。

    最值得注意的新功能是Modern运行时中的实例变量是“健壮的”:

    • 在Legacy运行时中,如果更改类中实例变量的布局,则必须重新编译从其继承的类。
    • 在Modern运行时中,如果更改类中实例变量的布局,则不必重新编译从其继承的类。

    此外,Modern运行时支持声明属性的实例变量合成(请参阅Objective-C编程语言中的声明属性)。

    2、平台

    OS X v10.5及更高版本上的iPhone应用程序和64位程序使用Modern版本的运行时。
    其他程序(OS X桌面上的32位程序)使用运行时的Legacy版本。

    二、与runtime交互

    Objective-C程序在三个不同的情况下与运行时系统交互:
    通过Objective-C源代码;
    通过Foundation框架的NSObject类中定义的方法;
    通过直接调用运行时函数。

    1、Objective-C源代码

    在大多数情况下,运行时系统会在后台自动运行。 您只需编写和编译Objective-C源代码即可使用它。

    编译包含Objective-C类和方法的代码时,编译器会创建实现该语言动态特性的数据结构和函数调用。 数据结构捕获在类和类别定义以及协议声明中找到的信息; 它们包括在Objective-C编程语言中定义类和协议中讨论的类和协议对象,以及方法选择器,实例变量模板和从源代码中提取的其他信息。 主要运行时函数是发送消息的函数,如Messaging中所述。 它由源代码消息表达式调用。

    2、NSObject方法

    Cocoa中的大多数对象都是NSObject类的子类,因此大多数对象都继承了它定义的方法。 (值得注意的例外是NSProxy类;有关更多信息,请参阅消息转发。)因此,其方法建立了每个实例和每个类对象固有的行为。但是,在少数情况下,NSObject类只定义了应该如何完成某事的模板;它本身并不提供所有必要的代码。

    例如,NSObject类定义一个描述实例方法,该方法返回描述类内容的字符串。这主要用于调试--GDB print-object命令打印从此方法返回的字符串。 NSObject对此方法的实现不知道该类包含什么,因此它返回一个包含对象名称和地址的字符串。 NSObject的子类可以实现此方法以返回更多详细信息。例如,Foundation类NSArray返回它包含的对象的描述列表。

    一些NSObject方法只是查询运行时系统的信息。这些方法允许对象执行内省。这种方法的例子是类方法,它要求对象识别它的类; isKindOfClass:和isMemberOfClass:,它测试对象在继承层次结构中的位置; respondsToSelector:,表示对象是否可以接受特定消息; conformsToProtocol :,表示对象是否声明实现特定协议中定义的方法;和methodForSelector :,它提供方法实现的地址。这些方法使对象能够自省内省。

    3、runtime的功能

    运行时系统是一个动态共享库,其公共接口由位于/ usr / include / objc目录中的头文件中的一组函数和数据结构组成。 其中许多函数允许您使用plain C来复制编写Objective-C代码时编译器所执行的操作。 其他构成了通过NSObject类的方法导出的功能的基础。 这些函数可以开发运行时系统的其他接口,并生成增强开发环境的工具; 在Objective-C中编程时不需要它们。 但是,在编写Objective-C程序时,一些运行时函数有时可能会有用。 所有这些功能都记录在Objective-C运行时参考中。

    三、消息

    本章介绍如何将消息表达式转换为objc_msgSend函数调用,以及如何按名称引用方法。 然后,它解释了如何利用objc_msgSend,以及如何 -如果需要 - 您可以绕过动态绑定。

    1、objc_msgSend函数

    在Objective-C中,消息在运行时之前不会绑定到方法实现。 编译器转换消息表达式;

    [receiver message]
    

    对消息传递函数objc_msgSend的调用。 此函数将接收者和消息中提到的方法的名称(即方法选择器)作为其两个主要参数:

    objc_msgSend(receiver, selector)
    

    消息中传递的任何参数也会传递给objc_msgSend:

    objc_msgSend(receiver, selector, arg1, arg2, ...)
    

    消息传递功能可以完成动态绑定所需的一切:

    • 它首先找到选择器引用的过程(方法实现)。 由于可以通过单独的类以不同方式实现相同的方法,因此找到的精确过程取决于接收者的类。
    • 然后它调用该方法实现,将接收对象(指向其数据的指针)以及为该方法指定的任何参数传递给它。
    • 最后,它将方法实现的返回值作为自己的返回值传递。
      (注意:编译器生成对消息传递功能的调用。 你永远不应该直接在你编写的代码中调用它。)

    消息传递的关键在于编译器为每个类和对象构建的结构。 每个Class结构都包括以下两个基本要素:

    • 指向父类的指针。
    • 类调度表。 此表具有将方法选择器与它们标识的方法的特定于类的地址相关联的条目。 setOrigin ::方法的选择器与(实现的过程)setOrigin ::的地址相关联,显示方法的选择器与显示的地址相关联,依此类推。

    创建新对象时,将分配其内存,并初始化其实例变量。 对象变量中的第一个是指向其类结构的指针。 这个名为isa的指针使对象可以访问它的类,并通过该类访问它继承的所有类。
    (注意:虽然严格来说不是语言的一部分,但isa指针是对象使用Objective-C运行时系统所必需的。 在结构定义的任何字段中,对象需要与结构objc_object(在objc / objc.h中定义)“等效”。 但是,您很少(如果有的话)需要创建自己的根对象,并且从NSObject或NSProxy继承的对象会自动拥有isa变量。)

    类和对象结构的这些元素如图3-1所示。


    图3-1消息传递框架

    当消息发送到对象时,消息传递函数遵循对象的isa指向类结构的指针,在该结构中它查找调度表中的方法选择器。如果它找不到那里的选择器,objc_msgSend跟随指向超类的指针并尝试在其调度表中找到选择器。连续失败导致objc_msgSend爬上类层次结构,直到它到达NSObject类。一旦找到选择器,该函数就会调用表中输入的方法并将接收对象的数据结构传递给它。

    这是在运行时选择方法实现的方式 - 或者在面向对象编程的术语中,方法动态地绑定到消息。

    为了加速消息传递过程,运行时系统在使用时后会缓存方法的选择器和地址。每个类都有一个单独的缓存器,它可以包含继承方法的选择器以及类中定义的方法。在搜索调度表之前,消息传递例程首先检查接收对象类的高速缓存(理论上可能会再次使用一次使用的方法)。如果方法选择器在缓存中,则消息传递仅比函数调用稍慢。一旦程序运行了足够长的时间来“预热”其缓存,它发送的几乎所有消息都会找到一个缓存的方法。随着程序的运行,缓存会动态增长以容纳新消息。

    2、使用隐藏参数

    当objc_msgSend找到实现方法的过程时,它会调用该过程并将其传递给消息中的所有参数。 它还传递了两个隐藏的参数:

    • 接收对象
    • 方法的选择器

    这些参数为每个方法实现提供有关调用它的消息表达式的两半的显式信息。 它们被称为“隐藏”,因为它们未在定义方法的源代码中声明。 它们在编译代码时插入到实现中。

    虽然这些参数没有显式声明,但源代码仍然可以引用它们(就像它可以引用接收对象的实例变量一样)。 方法将接收对象称为self,并将其自身的选择器称为_cmd。 在下面的示例中,_cmd引用strange方法的选择器,并指向接收strange消息的对象的self。

    - strange
    {
        id  target = getTheReceiver();
        SEL method = getTheMethod();
     
        if ( target == self || method == _cmd )
            return nil;
        return [target performSelector:method];
    }
    

    self是两个论点中更有用的。 实际上,它是接收对象的实例变量可用于方法定义的方式。

    3、获取方法地址

    规避动态绑定的唯一方法是获取方法的地址并直接调用它,就好像它是一个函数一样。 这种情况可能适用于极少数情况下,特定方法将连续多次执行,并且您希望每次执行该方法时都避免消息传递的开销。

    使用NSObject类,methodForSelector:中定义的方法,您可以请求指向实现方法的过程的指针,然后使用指针调用该过程。 methodForSelector:返回的指针必须小心地转换为正确的函数类型。 返回和参数类型都应包含在强制转换中。

    下面的示例显示了如何调用实现setFilled:方法的过程:

    void (*setter)(id, SEL, BOOL);
    int i;
     
    setter = (void (*)(id, SEL, BOOL))[target
        methodForSelector:@selector(setFilled:)];
    for ( i = 0 ; i < 1000 ; i++ )
        setter(targetList[i], @selector(setFilled:), YES);
    

    传递给过程的前两个参数是接收对象(self)和方法选择器(_cmd)。 这些参数隐藏在方法语法中,但在将方法作为函数调用时必须使其显式化。

    使用methodForSelector:绕过动态绑定可以节省消息传递所需的大部分时间。 但是,只有在特定消息重复多次的情况下,节省才会显着,如上面所示的for循环。

    注意,methodForSelector:由Cocoa运行时系统提供; 它不是Objective-C语言本身的一个特性。

    四、动态方法解析

    本章介绍如何动态提供方法的实现。

    1、动态方法解析

    在某些情况下,您可能希望动态提供方法的实现。 例如,Objective-C声明的属性功能(参见Objective-C编程语言中的声明属性)包含@dynamic指令:

    @dynamic propertyName;
    

    它告诉编译器将动态提供与属性关联的方法。

    您可以实现方法resolveInstanceMethod:和resolveClassMethod:分别为实例和类方法动态提供给定选择器的实现。

    Objective-C方法只是一个C函数,至少需要两个参数 - self和_cmd。 您可以使用函数class_addMethod将函数作为方法添加到类中。 因此,给出以下功能:

    void dynamicMethodIMP(id self, SEL _cmd) {
        // implementation ....
    }
    

    您可以使用解析InstanceMethod将其动态添加到类中作为方法(动态调用resolveThis方法):如下所示:

    @implementation MyClass
    + (BOOL)resolveInstanceMethod:(SEL)aSEL
    {
        if (aSEL == @selector(resolveThisMethodDynamically)) {
              class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
              return YES;
        }
        return [super resolveInstanceMethod:aSEL];
    }
    @end
    

    转发方法(如消息转发中所述)和动态方法解析在很大程度上是正交的。 类可以在转发机制启动之前动态解析方法。如果调用respondsToSelector:或instancesRespondToSelector:,则动态方法解析器有机会首先为选择器提供IMP。 如果您实现resolveInstanceMethod:但希望通过转发机制实际转发特定选择器,则为这些选择器返回NO。

    2、动态加载

    Objective-C程序可以在运行时加载和链接新类和类别。新代码被合并到程序中,并且与开始时加载的类和类别完全相同。

    动态加载可用于执行许多不同的操作。例如,系统首选项应用程序中的各种模块是动态加载的。

    在Cocoa环境中,动态加载通常用于允许自定义应用程序。其他人可以编写程序在运行时加载的模块 - 就像Interface Builder加载自定义调色板并且OS X系统首选项应用程序加载自定义首选项模块一样。可加载模块扩展了应用程序的功能。他们以您允许的方式为其做出贡献,但无法预测或定义自己。您提供框架,但其他人提供代码。

    虽然有一个运行时函数可以在Mach-O文件中执行Objective-C模块的动态加载(objc_loadModules,在objc / objc-load.h中定义),但Cocoa的NSBundle类为动态加载提供了一个非常方便的接口 - 一个是对象 - 面向并与相关服务集成。有关NSBundle类及其用法的信息,请参阅Foundation框架参考中的NSBundle类规范。有关Mach-O文件的信息,请参阅OS X ABI Mach-O文件格式参考。

    五、消息转发

    将消息发送到不能处理该消息的类对象是错误的。 但是,在报错之前,运行时系统为接收对象提供了处理消息的第二次机会。

    1、转发

    如果向未处理该消息的对象发送消息,则在报错之前,运行时会向对象发送一个forwardInvocation:消息,其中NSInvocation对象作为其唯一参数 - NSInvocation对象封装原始消息和传递的参数用它。

    您可以实现forwardInvocation:方法来为消息提供默认响应,或以其他方式避免错误。顾名思义,forwardInvocation:通常用于将消息转发给另一个对象。

    要查看转发的范围和意图,请设想以下方案:首先,假设您正在设计一个可以响应名为negotiate的消息的对象,并且您希望其响应包含另一种对象的响应。您可以通过将negotiate消息传递给您实现的negotiate方法主体中的其他对象来轻松完成此操作。

    更进一步,假设您希望对象对negotiate消息的响应完全是在另一个类中实现的响应。实现此目的的一种方法是让您的类从其他类继承该方法。但是,可能无法以这种方式安排事情。您的类和实现negotiate的类可能位于继承层次结构的不同分支中,这可能是有充分理由的。

    即使你的类不能继承negotiate方法,你仍然可以通过实现一个简单地将消息传递给另一个类的实例的方法版本来“借用”它:

    - (id)negotiate
    {
        if ( [someOtherObject respondsTo:@selector(negotiate)] )
            return [someOtherObject negotiate];
        return self;
    }
    

    这种做事方式可能会有点麻烦,特别是如果有许多消息要将对象传递给另一个对象。您必须实现一种方法来涵盖您希望从其他类借用的每种方法。而且,在您编写代码时,您可能无法处理您可能想要转发的完整消息集。该集合可能取决于运行时的事件,并且可能会在将来实现新方法和类时发生变化。

    forwardInvocation:消息提供的第二次机会为这个问题提供了一个不那么特别的解决方案,一个是动态的而不是静态的。它的工作方式如下:当一个对象无法响应消息,因为它没有与消息中的选择器匹配的方法时,运行时系统通过向对象发送一个forwardInvocation:消息来通知该对象。每个对象都从NSObject类继承一个forwardInvocation:方法。但是,NSObject的方法版本只调用doesNotRecognizeSelector:。通过重写NSObject的版本并实现自己的版本,您可以利用forwardInvocation:消息提供的机会将消息转发给其他对象。
    要转发消息,所有forwardInvocation:方法需要做的是:

    • 确定消息的去向,以及
    • 用它的原始参数发送它。

    可以使用invokeWithTarget:方法发送消息:

    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
        if ([someOtherObject respondsToSelector:
                [anInvocation selector]])
            [anInvocation invokeWithTarget:someOtherObject];
        else
            [super forwardInvocation:anInvocation];
    }
    

    转发的消息返回值将返回给原始发送方。 可以将所有类型的返回值传递给发送方,包括id,结构和双精度浮点数。

    forwardInvocation:方法可以充当无法识别的消息的分发中心,将它们分配给不同的接收者。 或者它可以是转移站,将所有消息发送到同一目的地。 它可以将一条消息翻译成另一条消息,或者只是“吞下”一些消息,这样就没有响应也没有错误。 forwardInvocation:方法还可以将多个消息合并到一个响应中。 什么forwardInvocation:取决于实现者。 但是,它为转发链中的对象链接提供了机会,这为程序设计开辟了可能性。
    (注意:forwardInvocation:方法只有在它们不调用标称接收器中的现有方法时才会处理消息。 例如,如果您希望对象将消息转发到另一个对象,则它不能拥有自己的协商方法。 如果是,则消息永远不会到达forwardInvocation:)

    有关转发和调用的更多信息,请参阅Foundation框架参考中的NSInvocation类规范。

    2、转发和多重继承

    转发模仿继承,并可用于向Objective-C程序提供多重继承的一些效果。 如图5-1所示,通过转发消息来响应消息的对象似乎借用或“继承”另一个类中定义的方法实现。


    图5-1转发

    在此图示中,Warrior类的实例将协商消息转发给Diplomat类的实例。战士似乎会像外交官一样进行谈判。它似乎会回应谈判的信息,并且出于所有实际目的,它确实做出了回应(虽然它确实是一个外交官正在做的工作)。

    转发消息的对象因此从继承层次结构的两个分支“继承”方法 - 它自己的分支和响应消息的对象的分支。在上面的例子中,似乎Warrior类继承自Diplomat以及它自己的超类。

    转发提供了您通常希望从多个继承中获得的大多数功能。但是,两者之间存在重要差异:多重继承在单个对象中组合了不同的功能。它倾向于大型,多方面的对象。另一方面,转发将不同的对象分配给不同的对象。它将问题分解为较小的对象,但以对邮件发件人透明的方式关联这些对象。

    3、代理对象

    转发不仅模仿多重继承,还可以开发代表或“覆盖”更多实质对象的轻量级对象。代理人代表另一个对象并向其传达消息。

    在Objective-C编程语言中的“远程消息传递”中讨论的代理就是这样的代理。代理负责将消息转发到远程接收器的管理细节,确保通过连接复制和检索参数值,等等。但它并没有尝试做太多其他事情;它不会复制远程对象的功能,而只是给远程对象一个本地地址,一个可以在另一个应用程序中接收消息的地方。

    其他种类的替代物也是可能的。例如,假设您有一个操纵大量数据的对象 - 也许它会创建一个复杂的图像或读取磁盘上文件的内容。设置此对象可能非常耗时,因此您更喜欢懒惰地执行此操作 - 在确实需要时或系统资源暂时空闲时。同时,您至少需要此对象的占位符才能使应用程序中的其他对象正常运行。

    在这种情况下,你最初可以创建,而不是完整的对象,但它是一个轻量级的代理。这个对象可以自己做一些事情,比如回答有关数据的问题,但大多数情况下它只是为较大的对象保留一个位置,并且当时间到来时,将消息转发给它。当代理的forwardInvocation:方法首先接收发往另一个对象的消息时,它将确保该对象存在并且如果不存在则创建它。较大对象的所有消息都通过代理,因此,就程序的其余部分而言,代理和较大的对象将是相同的。

    4、转发和继承

    虽然转发模仿继承,但NSObject类从不混淆这两者。 像respondsToSelector:和isKindOfClass这样的方法:只查看继承层次结构,而不是转发链。 例如,如果询问Warrior对象是否响应协商消息,

    if ( [aWarrior respondsToSelector:@selector(negotiate)] )
        ...
    

    答案是否定的,即使它可以毫无错误地接收谈判消息并在某种意义上通过将它们转发给外交官来回应它们。 (见图5-1)

    在许多情况下,NO是正确的答案。 但它可能不是。 如果使用转发来设置代理对象或扩展类的功能,则转发机制可能应该像继承一样透明。 如果您希望对象的行为就像它们真正继承了它们转发消息的对象的行为一样,那么您需要重新实现respondsToSelector:和isKindOfClass:方法来包含您的转发算法:

    - (BOOL)respondsToSelector:(SEL)aSelector
    {
        if ( [super respondsToSelector:aSelector] )
            return YES;
        else {
            /* Here, test whether the aSelector message can     *
             * be forwarded to another object and whether that  *
             * object can respond to it. Return YES if it can.  */
        }
        return NO;
    }
    

    除了respondsToSelector:和isKindOfClass:之外,instancesRespondToSelector:方法还应该镜像转发算法。 如果使用协议,则同样应将conformsToProtocol:方法添加到列表中。 类似地,如果一个对象转发它收到的任何远程消息,它应该有一个methodSignatureForSelector版本:它可以返回最终响应转发消息的方法的准确描述; 例如,如果一个对象能够将消息转发给它的代理,那么您将实现methodSignatureForSelector:如下所示:

    - (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
    {
        NSMethodSignature* signature = [super methodSignatureForSelector:selector];
        if (!signature) {
           signature = [surrogate methodSignatureForSelector:selector];
        }
        return signature;
    }
    

    您可以考虑将转发算法放在私有代码中的某个位置,并使用所有这些方法(forwardInvocation:included)来调用它。
    (注意:这是一项高级技术,仅适用于无法提供其他解决方案的情况。 它不是作为继承的替代品。 如果必须使用此技术,请确保完全了解执行转发的类的行为以及要转发的类)

    本节中提到的方法在Foundation框架参考中的NSObject类规范中进行了描述。 有关invokeWithTarget:的信息,请参阅Foundation框架参考中的NSInvocation类规范。

    六、类型编码

    为了协助运行时系统,编译器对字符串中每个方法的返回和参数类型进行编码,并将字符串与方法选择器相关联。 它使用的编码方案在其他上下文中也很有用,因此可以使用@encode()编译器指令公开使用。 当给定类型规范时,@ encode()返回编码该类型的字符串。 类型可以是基本类型,例如int,指针,标记结构或联合,或类名 - 实际上,任何类型都可以用作C sizeof()运算符的参数。

    char *buf1 = @encode(int **);
    char *buf2 = @encode(struct key);
    char *buf3 = @encode(Rectangle);
    

    下表列出了类型代码。 请注意,它们中的许多重叠与编码对象时用于存档或分发的代码重叠。 但是,此处列出的代码在编写编码器时无法使用,并且在编写不是由@encode()生成的编码器时可能需要使用代码。 (有关编码对象以进行存档或分发的详细信息,请参阅Foundation Framework参考中的NSCoder类规范)


    表6-1 Objective-C类型编码

    重要说明:Objective-C不支持long double类型。 @encode(long double)返回d,这与double的编码相同。

    数组的类型代码用方括号括起来; 数组中元素的数量是在数组类型之前的开括号之后立即指定的。 例如,一个包含12个浮点指针的数组将被编码为:

    [12^f]
    

    结构在括号内指定,括号内的联合。 首先列出结构标记,然后是等号,并按顺序列出结构字段的代码。 例如,结构

    typedef struct example {
        id   anObject;
        char *aString;
        int  anInt;
    } Example;
    

    编码如下:

    {example=@*i}
    

    相同的编码会导致定义的类型名称(示例)或结构标记(示例)是否传递给@encode()。 结构指针的编码带有关于结构字段的相同数量的信息:

    ^{example=@*i}
    

    但是,另一个间接级别会删除内部类型规范:

    ^^{example}
    

    对象被视为结构。 例如,将NSObject类名传递给@encode()会产生以下编码:

    {NSObject=#}
    

    NSObject类只声明一个类型为Class的实例变量isa。

    请注意,尽管@encode()指令不返回它们,但运行时系统使用表6-2中列出的其他编码来表示类型限定符,当它们用于在协议中声明方法时。


    表6-2 Objective-C方法编码

    七、属性声明

    当编译器遇到属性声明时(请参阅Objective-C编程语言中的声明属性),它会生成与封闭类,类别或协议关联的描述性元数据。 您可以使用支持在类或协议上按名称查找属性,将属性类型作为@encode字符串获取,以及将属性列表作为C字符串数组复制的函数来访问此元数据。 每个类和协议都有一个声明的属性列表。

    1、属性类型和功能

    Property结构定义了属性描述符的不透明句柄。

    typedef struct objc_property *Property;
    

    您可以使用函数class_copyPropertyList和protocol_copyPropertyList分别检索与类关联的属性数组(包括已加载的类别)和协议:

    objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
    objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)
    

    例如,给定以下类声明:

    @interface Lender : NSObject {
        float alone;
    }
    @property float alone;
    @end
    

    您可以使用以下方式获取属性列表:

    id LenderClass = objc_getClass("Lender");
    unsigned int outCount;
    objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
    

    您可以使用property_getName函数来发现属性的名称:

    const char *property_getName(objc_property_t property)
    

    您可以使用函数class_getProperty和protocol_getProperty分别获取对类和协议中具有给定名称的属性的引用:

    objc_property_t class_getProperty(Class cls, const char *name)
    objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)
    

    您可以使用property_getAttributes函数来发现属性的名称和@encode类型字符串。 有关编码类型字符串的详细信息,请参阅类型编码; 有关此字符串的详细信息,请参阅属性类型字符串和属性属性描述示例。

    const char *property_getAttributes(objc_property_t property)
    

    将这些放在一起,您可以使用以下代码打印与类关联的所有属性的列表:

    id LenderClass = objc_getClass("Lender");
    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
    for (i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
    }
    
    2、属性类型字符串

    您可以使用property_getAttributes函数来发现名称,属性的@encode类型字符串以及属性的其他属性。

    该字符串以T开头,后跟@encode类型和逗号,并以V结尾,后跟后备实例变量的名称。 在这些属性之间,属性由以下描述符指定,以逗号分隔:

    表7-1声明的属性类型编码

    有关示例,请参见下面属性属性描述示例。

    3、属性属性描述示例

    鉴于这些定义:

    enum FooManChu { FOO, MAN, CHU };
    struct YorkshireTeaStruct { int pot; char lady; };
    typedef struct YorkshireTeaStruct YorkshireTeaStructType;
    union MoneyUnion { float alone; double down; };
    

    下表显示了示例属性声明以及property_getAttributes返回的相应字符串:



    相关文章

      网友评论

        本文标题:Objective - c Runtime编程指南

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