Objective-C Runtime 文档翻译
前言
如果读到感觉不理解、晦涩的地方,或者想要交流的可以联系我QQ1325582826,Call me!欢迎赐教!
Objective-C语言尽可能多的将许多决定从编译连接推迟到运行时。无论何时,它都尽可能的动态处理事件。这就意味着OC语言不仅仅需要编译器,还需要一个运行时系统来执行编译完成的代码。对于OC而言,运行时系统扮演了操作系统的角色;就是它使得OC运行起来。
这个文档涉及到NSObject类和Objective-C程序如何与运行时系统互相作用。尤其是,对于动态加载新的类和向其他对象转发消息,本文档可用于检索编程示例。我们也可以从本文档查到在程序运行时,关于如何查找到对象相关的信息。
我们应该阅读此文档,以便加深(对OC运行时系统是如何工作的和如何利用它)的认知和理解。尤其是,我们在写Cocoa APP时,有必要阅读这份文档。
文档的结构
本文档有一下章节:
- Runtime Versions and Platforms( [译文](#Runtime Versions and Platforms) )
- Interacting with the Runtime ( [译文](#Interacting with the Runtime) )
- Messaging ( 译文 )
- Dynamic Method Resolution ( [译文](#Dynamic Method Resolution) )
- Message Forwarding ( [译文](#Message Forwarding) )
- Type Encodings ( [译文](#Type Encodings) )
- Declared Properties ( [译文](#Declared Properties) )
相关文档
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所示:
当一个消息被发送到某对象,这个消息函数查找指向该类结构体的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,看起来像是借或者“继承”了另外一个类的方法实现。
在这个插图中,一个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 String和Property 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
开机按钮著作,想交流的可以加下鄙人的QQ—1325582826