Objective-C Runtime 文档翻译

前言

 

若是读到感受不理解、晦涩的地方,或者想要交流的能够联系我QQ1325582826,Call me!欢迎赐教!html

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运行时在最近实现的变化。app

 

Runtime 版本和平台

 

在不一样的平台,有不一样版本的OC runtime。ide

 

旧的和如今的版本

 

有两个版本的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 。)所以它的方法创建了行为(对每一个实例和类对象来讲都是已经存在的方法实现)。少数状况下,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,参见类型编码;此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
相关文章
相关标签/搜索