zoukankan      html  css  js  c++  java
  • Objective-C Runtime 文档翻译

    前言

     

    如果读到感觉不理解、晦涩的地方,或者想要交流的可以联系我QQ1325582826,Call me!欢迎赐教!

    Objective-C语言尽可能多的将许多决定从编译连接推迟到运行时。无论何时,它都尽可能的动态处理事件。这就意味着OC语言不仅仅需要编译器,还需要一个运行时系统来执行编译完成的代码。对于OC而言,运行时系统扮演了操作系统的角色;就是它使得OC运行起来。

    这个文档涉及到NSObject类和Objective-C程序如何与运行时系统互相作用。尤其是,对于动态加载新的类和向其他对象转发消息,本文档可用于检索编程示例。我们也可以从本文档查到在程序运行时,关于如何查找到对象相关的信息。

    我们应该阅读此文档,以便加深(对OC运行时系统是如何工作的和如何利用它)的认知和理解。尤其是,我们在写Cocoa APP时,有必要阅读这份文档。

    文档的结构

     

    本文档有一下章节:

    相关文档

     

    Objective-C Runtime Reference描述了OC运行时库支持的数据结构和函数。我们变成可以使用这些接口和OC运行时系统交互。例如,我们可以添加类和方法,或者获取所有(已经加载的)类的定义的列表。
    Programming with Objective-C 描述了OC语言。
    Objective-C Release Notes 描述了OSX中,OC运行时在最近实现的变化。

    Runtime 版本和平台

     

    在不同的平台,有不同版本的OC runtime。

    旧的和现在的版本

     

    有两个版本的OC runtime——“旧版”和“现在版”。现在版就是OC-2.0并包含了许多新特性。旧版本的runtime的编程接口就是OC-1;现在版本的runtime全部接口参见 Objective-C Runtime Reference
    最值得注意的新特性是,现在版本的实例变量是“不脆弱的”:

    • 在旧版本runtime,如果我们改变一个类的实例变量的布局,我们必须重新编译所有继承自它的类。
    • 在现在版本runtime,如果我们改变一个类的实例变量的布局,我们不需要重新编译所有继承自它的类。

    另外,现在版本的runtime支持为声明的属性做实例变量的synthesis(参见Objective-C Programming Language)。

    平台

     

    iPhone应用和OSX 10.5版本的64-位编程使用现在版本的runtime。

    与runtime的相互作用

     

    OC编程和runtime系统的相互作用,可以分三个不同的标准:

    • 通过OC代码。
    • 通过在Foundation framework 的 NSObject类中定义方法。
    • 通过直接调用runtime 函数。

    OC代码

     

    这是最重要的一部分,runtime 系统在该场景背后自动运行。我们仅仅通过写和编译OC代码就可以使用runtime系统。
    当编译包含OC类和方法的代码时,编译器就会创建数据结构和(实现了语言动态特征)函数。数据结构能够捕获有Class和category以及protocol中声明的信息;它们包含了Class和Protocol(在Objective-C Programming Language 中定义的Class和Protocol,还有方法selectors、实例变量以及其他从源码中提取到的信息)。主要的runtime功能就是发送消息,参见 Messaging ,它也会被OC代码消息表达式调用。

    NSObject Methods

     

    许多Cocoa种的对象都是NSObject类的子类,因此许多对象继承了它定义的方法。(NSProxy类是个例外,更多信息参见[ Message Forwarding ](#Message Forwarding)。)因此它的方法建立了行为(对每个实例和类对象来说都是已经存在的方法实现)。少数情况下,NSObject类只定义了应该如何做的方法模板,它自身不提供所有的必须的代码。
    例如,NSObject类定义了description实例方法,该方法用于返回一个用于描述类内容的字符串,这主要是用于debugging—GDB print-object命令打印由该方法返回额字符串。NSObject的该方法的实现不知道该类包含什么,因此它返回一个包含了对象的名称和地址的字符串。NSObject的子类能够重写该方法并返回更详细的描述。例如,Foundation的NSArray类返回了一个array包含的所有的对象的列表。

    NSObject的一些方法仅仅查询runtime系统获取信息。这些方法使得对象能够执行校验。例如“class”方法,是用来查询对象的类型;isKindOfClass:和isMemberOfClass:,是测试对象在继承层次中的位置;respondsToSelector:,用于校验对象能否接收一个指定的消息;conformsToProtocol:,用于校验是否某个对象声明了指定Protocol中定义的方法的实现;methodForSelector:,用于提供方法实现体的地址。这些对象本身都是校验性的能力的方法。

    Runtime Functions

     

    运行时系统是动态共享库,并且头文件中(文件路径/usr/include/objc)有一系列函数和数据结构接口声明;其中大部分函数允许我们使用基本的C来复制那些编译器的实现(同我们以OC代码编译后的代码)。其他基础功能可以通过NSObject获得。这些功能能够让我们为runtime 系统开发其接口和工具,以便提高开发效率;在使用OC编程时也可以不使用runtime接口。不过,当用OC编写程序时,有些runtime函数功能在某些场合是非常有用的。所有runtime函数声明可参见Objective-C Runtime Reference

    消息机制

     

    本节讲述消息表达式是如何转换为objc_msgSend函数调用的,和如何通过name查找方法;然后会解释我们如何充分利用objc_msgSend,和如何避免动态绑定(如果有需要)。

    objc_msgSend 函数(消息函数)

     

    OC中,在运行时前,消息是不能确定方法的实现体地址的。编译器转换消息表达式,

    [receiver message]
    

    转换成消息函数,objc_msgSend。这个函数需要该消息表达式中的接收者(receiver)和方法的名字——方法的selector作为第二个主要的参数:

    objc_msgSend(receiver, selector)
    

    任何传入消息的参数都和一通过objc_msgSend处理:

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

    消息函数为动态绑定完成所有所需要的事情:

    • 首先找到方法实现 (method implementation) ,也就是selector所指向的地址。由于相同的方法在不同的类对应的方法实现也是不同的,所以准确的实现体的获得取决于reciver的类型。
    • 然后调用方法实现,将其(selector)传递给receiver (指向它的数据的指针) ,和该方法需要的参数。
    • 最终,返回函数的返回值。

    注意:编译器生成消息函数的调用。我们永远不要在自己写的代码中直接调用它(PS:文档中此处所说的注意在现实中,貌似只起到了提醒大家要确保消息发送正确的作用,慎重使用)。

    消息发送的核心在于编译器为每个类个对象创建的结构体,每个类结构体包含两个基本的要素:

    • 一个指向superclass的指针。
    • 一个类派发表。这个表包含所有的(该类所指定的方法的)相关的selectors。可以说,setOrigin::方法的selector就是(程序中的实现体)setOrigin::的地址,display方法和display的地址相关联,等等。
      当一个新的对象被创建时,就会申请(alloca)内存,它的实例对象会被初始化(initialized)。首先,在对象的变量中有一个指向它自身类结构体的指针,被称为isa,通过它能够让对象找到所属的类,通过所属的类可以查找所有该对象继承过程的类。

    注意:该语言有不严谨的一部分,isa指针是对象关联OC运行时系统所必须的。一个对象需要“等价”于一个结构体 objc_object (在objc/objc.h中定义的) ,包含所有该结构体中的分量。然而,我们很少创建我们自己的根类(root object),继承自NSObject或者NSProxy的对象会自动具备isa变量。

    Class和对象结构体拥有的基本元素如图3-1所示:

    图3-1 Messaging Framework

    当一个消息被发送到某对象,这个消息函数查找指向该类结构体的isa指针,在类结构体中查找到方法的分发表里的对应的selector;如果在此处找不到selector,objc_msgSend 顺着superclass的指针尝试在superclass中的分发表中查找selector。如果还没有查找到,那么objc_msgSend将会沿着类的继承层次向上寻找,直到NSObject类。一旦定位到selector,函数将会调用分发表里的方法,并将reciver 对象的数据结构体传递给它。

    这就是方法实现体在运行时选择的方式,以面向对象编程的术语说,methods(方法实现)就是动态绑定到message(消息)。

    为了加速消息发送的过程,runtime系统缓存selector和methods被使用的地址。每个类都有单独 的cache,它可以包含继承的方法的selector,也会包含在该类中定义的方法。在搜索分发表之前,消息机制会先检查receiver 对象类的cache(理论上,被使用过一次的method很有可能被再次调用);如果selector是在cache里面的,消息发送就只是稍微慢于函数调用。一旦程序运行了足够长时间使得它的类的caches“完全活跃”,基本上消息发送就是通过查找cache里的method完成了。为了容纳新的消息发送,Caches随着程序的运行动态增长。

    使用隐藏的参数

     

    objc_msgSend找到method的实现体时,他就会调用实现体,并将消息中的所有参数传递给他,其中也包含以下两个参数:

    • receiver 对象。
    • method的selector。

    Method的实现体将会从消息表达式中获得这两个参数。这两个参数被称为“隐藏的”,是因为它们没有在method中的源码中声明。它们在编译的时候被插入实现体。

    尽管这两个参数是隐式声明的,源代码仍旧能够引用他们(由于它能够指向receiver 对象的实例变量)。一个method引用receiver对象就是self(对象本身),对于它自己的selector就是_cmd。下面的例子中,_cmd指向strange方法的selector,self指向接受strange消息的对象。

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

    self是这两个参数中最有用的。本质上来说,就意味着,receiver对象的实例变量对于method是可用的。

    获取Method的地址

     

    唯一避免动态绑定的方式就是获得method的地址并直接把它当做一个函数调用。如果某个method需要连续的调用多次,并且我们想要避免之前每次method被执行时的消息发送环节,此时避免动态绑定是有必要的。

    通过使用NSObject类的methodForSelector:方法,我们可以查找method的实现体的指针,然后使用这个指针调用实现体。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);
    
    

    传递到函数的前两个参数是receiver对象(self)和method selector(_cmd)。这些参数是隐藏在method语法中,但是当method被作为函数调用时是必须要显示传递的。
    使用methodForSelector:能够避免动态绑定,并节省消息发送机制所需要的时间。然而,仅仅当执行要被重复多次的特殊的消息(就像上面的for循环展示一样)这种节省时间的才有意义。

    注意methodForSelecotor:是被Cocoa runtime系统提供的,而不是OC语言的特征。

    动态方法的原理

     

    本节主要讲如何为method提供动态的IMP(实现)。

    动态Method的原理

     

    有时候我们需要为method动态地提供IMP。例如OC声明属性特征(Declared Properties in The Objective-C Programming Language)包括 @dynmaic 指令:

    @dynamic propertyName;
    

    这将会告诉编译器,这个属性关联的methods将会动态提供。
    我们能够通过实现methodsresolveInstanceMethod:resolveClassMethod:来分别为实例或者类method指定的selector提供IMP。

    一个OC的method就是简单的C函数,只不过这个C函数至少有两个参数——self和_cmd。我们能够使用函数class_addMethod为一个类添加一个函数。例如下面的函数:

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

    我们能够动态的为一个类添加一个method(调用resolveThisMethodDynamically)使用resolveInstanceMethod:如下:

    @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
    

    转发methods(就像Message Forwarding中介绍的)和动态方法的原理基本是一样的。一个Class在转发机制前还是有方法动态处理method的。如果respondsToSelector:或者instancesRespondToSelector是被调用的,此时,动态method是被给一次机会为selector提供IMP。如果我们实现resolveInstanceMethod:,但是想要某个特殊方法继续消息转发机制,我们应该为这些selector返回NO。

    动态加载

     

    一个OC程序能够在运行的时候,加载和连接新的类和分类。新的代码是被合并到程序内并和最初加载的类和分类同等对待。
    动态加载能够被用于做许多不同的事情。例如,系统设置APP里的不同的模块是被动态加载的。
    在Cocoa环境中,动态加载通常被用于让APP更个性化。我们程序能够在运行时加载第三方写的模块——尽管Interface Buider加载自定义的工具,以及OSX偏好设置APP加载自定义的偏好模块。可加载的模块扩展了我们APP的功能,它们在经过APP允许的情况下才起作用,但是也有难以预测的问题。
    尽管,有一个runtime功能是为在Mach-O文件中的OC模块执行动态加载(objc_loadModules,在objc/objc-load.h中定义),Cocoa的NSBundle类为动态加载提供大量的更便利的接口——面向对象并集成了相关服务。通过查看NSBundle类在Foundation framework中的说明,查找NSBundle类和它的使用。相应的对于Mach-O文件查看OSX ABI Mach-O 文件格式参考资料。

    消息转发(Message Forwarding)

     

    将消息发送给一个对象,并且对象没有处理这个消息,就会产生一个错误。但是,在宣告错误之前,运行时系统给receiver 对象第二次机会去处理消息。

    转发(Forwarding)

     

    如果将消息发送给一个object,并且object没有处理这个消息,在宣告错误之前runtime将forwardInvocation:消息和唯一一个参数即NSInvocation对象发送给object;NSInvocation对象封装了最初的消息和传递过来的参数。

    我们能够实现forwardInvocation:方法为消息提供一个默认的相应,或者以某种方式避免这种错误。见名知意,forwardInvocation:通常被用于将消息转发到其他对象。

    为了了解转发的能力范围和目的,想想一下场景:假设,首先,我们设计一个对象能够响应一个叫做negotiate的消息,并且我们我们希望这个消息的响应中包含另外一种对象的响应。通过在negotiate的实现体中将negotiate消息传递给另外的对象,我们能够轻松的完成这个任务。
    进一步想想,并假设我们想要我们的object为一个negotiate消息做出的响应,是另外一个类的实现体。一种实现方式是让我们的类继承另外的类。然而,我们没必要这么做,因为当前的类和实现了negotiate的类是在不同的继承层次分支上。
    即使我们的类不通过继承也能获得negotiate方法,我们能够“借”到这个方法,简单的版本就是通过将消息传递给其他类的实例:

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

    这种方式显得比较笨重,尤其是,如果我们有大量的消息需要object传递给其他的对象。我们不得不以这种方式重写每个我们要“借”的方法。此外,他将不可能处理我们漏掉或者不知道的方法;即便我们将包含object所有消息的集合都以这种形式复写了,可是这些消息都是依赖于运行时的,并且在未来某个时刻他们可能变成了由新的method和类响应。

    由forwardInvocation:消息提供的第二次机会,针对此问题,提供更少量代码的解决方案,并且是动态的而不是静态的。它将像这样:当一个对象由于它没有匹配selector的method而不能响应某个消息时,系统将通过发送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];
    }
    
    

    被转发的消息的返回值会被返回到原来的发送者。所有类型的返回值能够被传递到发送者,包括ids,结构体,和双精度floating数字。
    一个forwardInvocation:方法能够用作于未识别的消息的分配中心,将他们发给不同的receiver;或者作为一个转发站,将消息发送到相同的目的地。它也可以将一个消息转义为别的消息;或者简单的“吃掉”某些消息,使得既没有相应也没有错误。一个forwardInvocation:方法也能讲几个消息结合起来,获得一个响应。forwardInvocation:能够做什么取决于实现这。它为转发链条的对象开启了加入编程设计的机会。

    注意:仅当它们无法调用receiver内存在的method时,forwardInvocation:方法才会处理消息。例如,我们想要我们的object转发negotiate消息到另外的object,首先我们的object不能有negotiate方法,如果有,消息将不会到达forwardInvocation:。

    更多信息关于转发和调用可以参见NSInvocation类的说明。

    转发和多继承

     

    转发可以模拟继承,可以用于提供多继承的效果。像图5-1中,一个通过转发响应图中消息的object,看起来像是借或者“继承”了另外一个类的方法实现。

    图5-1 Messaging Framework

    在这个插图中,一个Warrior类的实例将negotiate消息转发给一个Diplomat类的实例。Warrior将会看起来像Diplomat的negotiate实现,Warrior看起来好像响应了negotiate消息(尽管实际上是Diplomat响应的)。

    转发了消息的对象也因此“继承”了来自多重继承的两个分支——它自己的分支和实际响应消息的object。在上面的例子中,Warrior看起来好像它同时继承了Diploma和它自己的superclass。

    转发提供许多特性,典型的就是多继承:但是,多继承和转发是两个不同的事物:多继承将多个类的功能集结在一个对象上,它变得更大,多个对象的结合体;而转发,换句话说,是将某些响应与不相干的对象关联起来。它将问题分解成更小的目标,并将这些小目标与消息发送者关联起来。

    代替品对象

     

    Forwarding不仅可以模拟多继承;通过forwarding,我们可以用轻量级对象作为实质对象的“封面”或代表。代替品代替其他对象并接收发送到它的消息。

    在Objective-C Programming Language中被称为“远程消息”的proxy就是一个代替品。一个proxy涉及到对的管理细节有:forwarding消息到远程receiver,在连接过程时确保参数值是被拷贝的和重新获取的,等等。但是它也不会尝试去做更多别的;它不复制远程object的函数功能只是给远程object一个本地地址,也就是它能在别的APP中接受消息的地址。

    还有些其他类型的代替品objects。例如,假设,我们有一个object,它是用来处理大量数据的——可能它创建了复杂的图片或者从硬盘中的一个文件里读取内容。配置这个object可能是很费事间的,因此更倾向于懒加载——当真正需要它时或者系统资源是暂时闲置时。同时,为了APP中其他objects正常运行,我们需要为这个object提供至少一个占位object。
    在这种情况下,我们在最初可以创建不完全健全的object,代替的为它创建一个轻量级的对象。这个对象能够独自处理一些事情,例如请求数据,但是大多数情况下它只是作为一个为大object的占位,当消息来了,就将消息转发给它。当代替品的forwardInvocation:方法第一次接收到发往其他object的消息时,代替品将会先确认那个object是否存在,如果不存在就创建它。所有发往重量级object的消息都经过代理,因此剩下的程序中,代替品和重量级object在使用上是一样的。

    Forwarding和继承

     

    尽管forwarding可以模拟继承,NSObject类绝不会混淆这两者。像respondsToSelector:和IsKindOfClass:可以通过继承层次使用,却不能通过forwarding链使用。例如,如果Warrior object被查询是否响应negotiate消息,

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

    这个返回值将会是NO,即使它能够接收negotiate消息并不报错的响应该消息,例如通过forwarding 消息到Diplomat。( 见图5-1

    大多数情况下,正确的答案是NO。但是也可能不是,如果我们我们设置一个代替品object来扩展一个类的能力,forwarding机制会像继承一样。如果我们想要想要我们的objects像真的继承了(它们转发消息的目标)对象;我们需要重新实现respondsToSelector:和isKindOfClass:方法来包含我们的forwarding规则。

    
    - (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:方法也应该反应出forwarding规则。如果协议是被使用的,conformsToProtocol:方法应该同样的被添加到重写的列表中。相似的,如果一个object forwarding任何它接收到的远程消息,他应该有一个methodSignatureForSelector: ,这能够精确的返回最终响应forwarded消息的描述;例如,如果一个object是有能力将消息forward到他的代替品,我们应该实现methodSingnatureForSelector:就像以下:

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

    我们应该尽量吧forwarding规则的代码一起放在某处,包括forwardInvocation:。

    这个高级技巧,仅适合真的没有别的解决方案时使用。它不是继承的替代品。如果我们不得不使用这种技巧,就确保完全掌握这些运转规律(做转发的类和被转发的类关于forwarding的机制)。

    在本节中提到的method可以查看详细说明(NSObject )。更多关于invokeWithTarget:,参见NSInvocation类的说明。

    类型编码(Type Encodings)

     

    为了协助runtime系统,编译器将每个method的返回值类型和参数类型编码成一个特征string,并把这个string与method selector结合起来。这个编码方案在其他环境也是可用的,并且是公开用于 @encode() 编码指令。当给定一个type 说名,@encode() 返回一个string 编码该type;type可以使int、一个指针(pointer)、一个带有标签的structure或者union、或者一个Class名称——任何类型,事实上,就是所有可用于作为C的sizeof()操作的类型。

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

    下面的表列出了类型编码。注意,当为归档和解档编码对象时,以下大部分是和编码一致的。不过,有些在写编码器时不能使用的编码也被列出来了;并且,也有一些编码不是通过 @encode 生成的。(关于更多归档和解档编码objects,参见NSCoder 类说明。)

    表6-1 OC type encodings

    编码 类型
    c A char
    i An int
    s A short
    l A long 。 l is treated as a 32-bit quantity on 64-bit programs
    q A long long
    C An unsigned char
    I An unsigned int
    S An unsigned short
    L An unsigned long
    Q An unsigned long long
    f A float
    d A double
    B A C++ bool or a C99 _Bool
    v A void
    * A character string (char *)
    @ An object (whether statically typed or typed id)
    # A class object (Class)
    : A method selector (SEL)
    [array type] An array
    {name=type...} A structure
    (name=type...) A union
    b (num) A bit field of num bits
    ^ A pointer to type
    ? An unknown type (among other things, this code is used for function pointers)

    OC不支持long double类型。@encode(long double) 返回 d,就是说和double的编码一致。

    Array的编码是闭合的中括号;array中的元素在开括号紧接着后面。例如,包含12个指向float的指针编码如下:

    [12^f]
    

    Structures是使用大括号,unions使用小括号;structure的标记(struct)是被列出来的首先,然后是大括号,以及被列在其中的元素。例如:

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

    将会被编码为:

    {example=@*i}
    

    无论是 @encode() 传入的是type 名称(Example)或结构体标记(example),都会得到相同的编码。为结构体指针的编码如下:

    
    ^{example=@*i}
    
    

    不过,更高阶的结构体指针,将不会显示结构体内在的类型:

    
    ^^{example}
    
    

    Objects 是被视为结构体,例如将NSObject类名传入 @encode():

    
    {NSObject=#}
    
    

    NSObject类只声明了一个示例变量,isa,也就是Class类型的

    注意,尽管存在 @encode() 指令不返回值,runtime系统会使用额外的编码列表,如表6-2,当类型限定符被用于声明protocol中的methods时。

    表6-2 OC method encodings

    编码 类型
    r const
    n in
    N inout
    o out
    O bycopy
    R byref
    V oneway

    声明的属性

     

    当编译器遇见属性声明(参见Objective-C Programming Language 中的声明的属性),它生成与闭合的类、分类、Protocol相关联的描述性的元数据。我们能够获取这些元数据通过使用函数,这些函数支持借助类或Protocol的name查找属性,获取属性的type(就像 @encode 获得的string),拷贝一个由C string构成的数组的property 的attributes。声明的属性列表对每个Class和Protocol都是可获取的。

    Property Type 和函数

    Property结构体为property 描述符号定义一个不透明的操作。

    typedef struct objc_property *Property;
    

    我们能够使用class_copyPropertyList和protocol_copyPropertyList来分别获取与Class(包括被夹在的分类)以及Protocol相关联的属性数组。

    
    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函数查找属性的name:

    
    const char *property_getName(objc_property_t property)
    
    

    我们可以使用class_getProperty和protocol_getProperty,借助一个类中指定的name获取类或Protocol中的一个property的引用。

    
    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_getAtributes函数获取一个property的name和 @encode 类型string。更详细的编码类型string,参见[类型编码](#Type Encodings);此string更详细参见Property Type StringProperty Attribute Description

    
    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
    ", property_getName(property), property_getAttributes(property));
    }
    
    

    Property Type String

    我们可以使用property_getAttributes函数获取到property的name和 @encode 类型的字符串,以及property的其他特征。

    String以T开头,之后跟随着 @encode type 和一个逗号;最后面跟随一个V,之后是实例变量的name。在这两块之间,会插入下面的描述符,通过逗号间隔:

    Table7-1声明property type 编码

    Code Meaning
    R The property is read-only (readonly).
    C The property is a copy of the value last assigned (copy).
    & The property is a reference to the value last assigned (retain).
    N The property is non-atomic (nonatomic).
    G The property defines a custom getter selector name. The name follows the G (for example,GcustomGetter,).
    S The property defines a custom setter selector name. The name follows the S (for example, ScustomSetter:,)
    D The property is dynamic (@dynamic).
    W The property is a weak reference (__weak).
    P The property is eligible for garbage collection.
    t Specifies the type using old-style encoding.

    更多实例参见Property Attribute Description

    Property Attribute Description 示例

    预先给定这些定义:

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

    下面的表展示了示例property声明和property_getAttributes返回的string:

    Property 声明 Property 说明符
    @property char charDefault; Tc,VcharDefault
    @property double doubleDefault; Td,VdoubleDefault
    @property enum FooManChu enumDefault; Ti,VenumDefault
    @property float floatDefault; Tf,VfloatDefault
    @property int intDefault; Ti,VintDefault
    @property long longDefault; Tl,VlongDefault
    @property short shortDefault; Ts,VshortDefault
    @property signed signedDefault; Ti,VsignedDefault
    @property struct YorkshireTeaStruct structDefault; T{YorkshireTeaStruct="pot"i"lady"c},VstructDefault
    @property YorkshireTeaStructType typedefDefault; T{YorkshireTeaStruct="pot"i"lady"c},VtypedefDefault
    @property union MoneyUnion unionDefault; T(MoneyUnion="alone"f"down"d),VunionDefault
    @property unsigned unsignedDefault; TI,VunsignedDefault
    @property int (*functionPointerDefault)(char *); T^?,VfunctionPointerDefault
    @property id idDefault;Note: the compiler warns: "no 'assign', 'retain', or 'copy' attribute is specified - 'assign' is assumed" T@,VidDefault
    @property int *intPointer; T^i,VintPointer
    @property void *voidPointerDefault; T^v,VvoidPointerDefault
    @property int intSynthEquals;In the implementation block:@synthesize intSynthEquals=_intSynthEquals; Ti,V_intSynthEquals
    @property(getter=intGetFoo, setter=intSetFoo:) int intSetterGetter; Ti,GintGetFoo,SintSetFoo:,VintSetterGetter
    @property(readonly) int intReadonly; Ti,R,VintReadonly
    @property(getter=isIntReadOnlyGetter, readonly) int intReadonlyGetter; Ti,R,GisIntReadOnlyGetter
    @property(readwrite) int intReadwrite; Ti,VintReadwrite
    @property(assign) int intAssign; Ti,VintAssign
    @property(retain) id idRetain; T@,&,VidRetain
    @property(copy) id idCopy; T@,C,VidCopy
    @property(nonatomic) int intNonatomic; Ti,VintNonatomic
    @property(nonatomic, readonly, copy) id idReadonlyCopyNonatomic; T@,R,C,VidReadonlyCopyNonatomic
    @property(nonatomic, readonly, retain) id idReadonlyRetainNonatomic; T@,R,&,VidReadonlyRetainNonatomic

    注意,关于(非id)Class类property_getAtributes如下:

    @property (nonatomic,strong)NSString * maStingClass;
    
    
    fprintf(stdout, "%s", property_getAttributes(property));
    获得的字符串为:
    T@"NSString",&,N,V_maStingClass
    
  • 相关阅读:
    Python学习手册-24~27章
    80211MAC基础
    数据库简单操作
    Linux流量管理
    Docker 初识
    TLA+(待续...)
    计数器统计
    《数据挖掘导论》目录
    信用卡欺诈案例——机器学习实战
    从重采样到数据合成:如何处理机器学习中的不平衡分类问题?
  • 原文地址:https://www.cnblogs.com/zhouyubo/p/8435126.html
Copyright © 2011-2022 走看看