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
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\n", 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
posted @ 2018-02-10 16:20  开机按钮  阅读(689)  评论(2编辑  收藏  举报
levels of contents