Objective-C Runtime 编程指南

简介

Objective-C 从编译、链接接到运行需要遵守许多规则,只要有可能,它会动态的做一些事情。这意味着代码需要的不仅仅是一个编译,而且也有一个运行时系统去执行被编译的代码。运行时系统的行为作为Objective-C语言的一种操作方式。也是这门语言的工作方式。

Objective-C Runtime的双重本质
1. Objective-C Runtime 是 Objective-C 这门语言的工作方式;
2. Objective Runtime 是代码的一种运行环境。

本文着眼于NSObject类,以及Objective-C如何与运行时系统交互。尤其是检索了动态加载新类以及转发消息給其他对象的范式。也提供了当程序运行的时候如何寻找那些对象的信息。

你应该通过本文去理解运行时系统是如何工作的以及如何使用它。特别是,你需要知道和理解本文,以便去写一个Cocoa应用。

文章结构

  • 运行时的版本和平台 — 传统版和现代版的区别与使用平台
  • 运行时交互 — 与运行时交互的三种方式
  • 消息传递 — 一个消息如何找到它的实现
  • 动态方法解析 — 如何为一个类动态的添加方法
  • 消息转发 — 如何将消息转发给另一个响应对象
  • 类型编码 — 编译器对参数、返回值、方法的编码方式
  • 属性声明 — 属性的检索及描述方式

运行时的版本和平台

Objective-C 运行时在不同的平台有不同的版本。

传统版和现代版

Objective-C 有两个不同的版本—“传统版”和“现代版”。现代版是Objective-C 2.0,包含了许多新的功能。对于传统版的运行时编程接口被描述在Objective-C 1;现代版的运行时接口描述在Objective-C Runtime Reference

最值得注意的新功能是实例变量在现代版中是“非脆弱的”:

  • 在传统版运行时,如果你改变了实例变量在类中的布局,你必须重新编译从它这里继承的子类。
  • 在现代版运行时,如果你改变了一个类中实例变量的布局,你不必重新编译从它这里继承的子类。

另外,现代版运行时对声明的属性支持实例变量合成(看Declared Properties in The Objective-C Programing Language)。

平台

现代版:iPhone 程序和OS X v10.5的64-bit程序。
传统版:OS X 桌面32-bit程序。

运行时交互

Objective-C 程序与运行时交互在三个不同的层级:

  • 通过Objective-C 源码;
  • 通过定义在 NSObject 类的方法,该类在 Foundation framework;
  • 直接调用运行时函数。

Objective-C 源码

大多数情况下,运行时系统会自动的在幕后工作。你使用它仅仅通过写和编译 Objcetvie-C 源码。

当你编译包含 Objective-C 类和方法的代码时,编译器创建数据结构和函数调用来实现语言的动态特性。这些数据结构捕获在类、分类、协议中声明的信息;他们包含类和协议对象(在《The Objecive-C Programing Language》的Defineing a Class and Protocols有描述),也包含方法选择器,实例变量模版,从源码中提取的其它信息。运行时的主要功能是发送消息,如 Messaging所述。运行时通过源代码消息表达式被调用。

如图所示
Messaging

NSObject 方法

在 Cocoa 中,大部分对象是 NSObject 类的子类,所以大部分对象继承了它所定义的方法。(值得注意的是除了NSProxy类;更多的信息看Message Forwarding)因此,它的方法建立了每个实例和每个类对象的固有行为。然而,在少数情况下,NSObject 类仅仅定义了怎样做的模版;它并没有提供代码本身所有的需要。

例如,NSObject 类定义了一个名为 description 的实例方法,它返回类的内容的字符串描述。这个主要用于调试。调试器(GDB)print-object要求打印一个从这个方法返回的字符串。这个方法并不知道类里面包含什么内容,所以它会返回一个对象的名字和地址。NSObject 的子类能实现这个方法来返回更详细的信息,例如,Foundation 库里面的 NSArray 可以返回它所包含的对象列表描述。

 NSLog(@"ViewContolller-description:%@",self.description);
// Prints "ViewContolller-description:<ViewController: 0x7f869ce0a920>"
 NSArray *array = @[@"a",@"b",@"c"];
 NSLog(@"array-description:%@",array.description);
// Prints "array-description:(
    a,
    b,
    c
)"

一些 NSObject 方法简单的查询运行时系统的信息。这些方法允许对象去执行内省。像一些类方法,请求一个对象来识别它的类; isKindOfClass: 和 isMemberOfClass: 测试一个对象在继承层次中的位置;respondsToSelector:,表示哪一个对象能接受一个特定的消息;conformsToProtocol:,表示某一个对象是否声明了一个特定协议的方法实现;和methodForSelector:,提供一个方法实现的地址。像这些方法提供给一个对象使其具备自省性。

运行时函数

运行时系统是一个动态的共享库,它所组成的函数和数据结构的公共接口头文件在/usr/include/objc 目录下。许多函数允许你使用纯C代码来复制当你在写 Objective-C 时编译器所做的事情。其他的形式作为 NSObject 类的方法导出函数的基础。这些函数使开发运行时系统的其他接口成为可能以及生成增强开发环境的工具;在用 Objcetvie-C 编程时不需要他们。然而,一些运行时函数可能某些情况下有用。所有这些函数记录在Objective-C Runtime Reference 文档里。

消息传递

本章描述了将消息表达式转变为 objc_msgSend 函数调用,以及你如何通过名字判断方法。然后解释了你该如何利用 objc_msgSend, 如果需要的话,你如何绕过动态绑定。

objc_msgSend 函数

在Objective-C中,消息直到运行时才会绑定方法实现,编译器转换一个消息表达式,

[receiver message]

内部调用一个消息传递函数,objc_msgSend. 这个函数接受了函数的receiver和消息中提到的方法名,即方法选择器,作为它的两个特定参数:

objc_msgSend(receiver, selector)

任何其他通过 message 传递的参数也被 objc_msgSend 管理:

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

消息传递函数为动态绑定做了每一件事:
* 它首先查找选择器引用的过程(即方法的实现)。由于不同的类可能实现相同的方法,具体的过程取决于 receiver 的等级。
* 然后它调用方法的实现,将接收对象传递(一个指向方法实现的指针),以及该方法所指定的任何参数。
* 最后,运行时消息会传递程序运算的结果作为自己的返回值。

注意
编译器产生对消息传递函数的调用。你不应该在编写的代码中直接调用它。

消息传递的关键在于编译器为每一个类和对象构建的结构体。每个类结构体包含两个基本元素:

  • 一个指向父类的指针 。
  • 一个类调度表。这个表有和标识特定方法地址相关联的方法选择器的条目。如方法选择器为setOrigin::方法关联setOrigin::的地址(程序的实现),方法选择器为display方法关联display的地址,等等。

当一个新的对象被创建时,即为对象分配内存,它的实例变量被初始化。对象的变量当中首先是指向其结构体的指针。这个指针,被称为isa,用于对象访问它的类,通过本类,可以找到它继承的所有类。

注意
虽然不是严格意义上的语言的一部分,isa指针是一个对象与 Objcetvie-C 运行时系统一起工作所必需的。在结构体定义的任何字段中,一个对象需要与一个struct objc_object(定义在objc/objc.h)相同。然而,极少的情况下,如果有的话,需要创建自己的根对象,继承于NSObject 或NSProxy的对象会自动携带isa变量。

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

图3-1 消息传递架构
MessagingFramework

当消息被发送到一个对象时, 消息传递函数跟随着对象的isa指针到类的结构体,它在调度表里找到方法选择器。如果它在调度表中没有发现选择器,objc_msgSend 跟随指针到父类,试着在父类的调度表里寻找选择器。连续的失败将使objc_msgSend向上一级类查找,直到 NSObject 类。一旦它定位到选择器,函数就会调用在表中的方法实现, 将接收对象的数据结构传递给方法的实现。

这就是在运行时方法实现被选中的方式,用面对对象的术语来说,方法实现是动态绑定到消息。

为了加快消息传递处理, 运行时系统缓存了它们所用过的选择器和方法的地址。每个类有单独的缓存,它还可以包含被继承方法的选择器以及在类中定义的方法。查询调度表之前,消息传递首先会检查接收对象类的缓存(理论上一个方法用过一次就可能会被再次用到)。如果方法的选择器在缓存中,消息传递只是比函数调用稍慢。一旦一个程序已经运行了足够长的时间来预热缓存,那么几乎它所发送的所有消息都会发现已经被缓存的方法。当程序运行时缓存会动态的增长来容纳新的消息。

隐藏参数的使用

objc_msgSend发现方法的实现时,它调用这个方法并传递给它消息里携带的所有参数。它也传递了两个隐藏参数:

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

这些参数给到方法实现明确的信息调用它的消息表达式的两部分。之所以称为“隐藏”是因为在定义方法的代码里并没有被声明。当代码被编译时,它们被插入方法的实现。

虽然这些参数没有被显式的声明,源代码仍然能引用它们(就像它可以引用接收对象的实例变量一样)。一个方法引用接收对象作为self,和它自己的选择器作为_cmd.在以下的例子中,_cmd为strange 方法引用选择器,self为对象接收一个strange 消息。

- strange
{
id target = getTheReceiver();
SEL method = getTheMethod();

if ( target == self || method == _cmd )
return nil;
return [target performSelector: method];
}

这两个参数self更有用。实际上,它是在方法实现中访问接收对象的实例变量的方式。

获取方法地址

规避动态绑定的唯一方式是获取方法的地址,像调用一个函数一样直接调用它。在极少数情况下这是合适的,如当特定的方法被连续执行很多次时,你希望在每次执行方法时避免消息的开销。

用一个定义在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 语言本身的特性。

动态方法解析

本章描述了如何动态地提供一个方法的实现。

动态方法解析

有些情况下你可能想提供一个动态的方法实现。例如,Objective-C 声明属性特性(在 The Objective-C Programming Language 看属性声明)包含@dynamic指令:

@dynamic propertyName;

告诉编译器将动态地提供与属性相关的 getter、setter 方法。也就是说,编译器不再为我们提供默认的 setPropertyName:和 propertyName 方法,而由我们自己实现。

你可以实现方法 resolveInstanceMethod: 和 resolveClassMethod: 来分别的为实例方法和类方法动态的提供一个 selector 的实现。因为当 Cache 和方法分发表中(包括超类)找不到要执行的方法时,Runtime 会调用 resolveInstanceMethod:和 resolveClassMethod: 来给程序员一次实现方法的机会。

一个 Objective-C 方法仅仅是一个至少携带两个参数( self 和 _cmd )的C函数。你可以使用 class_addMethod 函数来为类增加一个函数作为方法。因此,鉴于以下函数:

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

你能够使用resolveInstanceMethod: 动态地添加它到一个类作为一个方法( resolveThisMethodDynamically 被调用),像这样:

@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

我们来看一下 class_addMethod(::::) 函数:
作用:给一个类动态添加方法。

声明
func class_addMethod(_ cls: AnyClass?,
_ name: Selector,
_ imp: IMP,
_ types: UnsafePointer?) -> Bool

parameters
cls: to add method for the class.
name: a selector that specifies the name of the method being added.
imp: method’s implementation. take at least two arguments: self and _cmd.
types: An array of characters that describe the types of the arguments to the method.For possible values ,see Objective-C Runtime Programming Guide > Type Encodings. Since the function must take at least two arguments — self and _cmd, the second and third characters must be “@:”(the first character is return type.)

return value: bool
true if the method was added successfully. otherwise false (for example, the class already contains a method implementation with the name).

discussion
class_addMethod(::::) will add an override of a superclass’s implementation,but will not replace an existing implementation in this class. To change an existing implementation , use method_setImplementation(::).

转发方法(如Message Forwarding 所述)和动态方法解析基本上是正交的。一个类有机会在转发机制启动之前动态解析一个方法。如果 respondsToSelector: 或 instancesResponseToSelector: 被调用,动态方法解析器有机会首先为选择器提供一个IMP。如果你实现了resolveInstanceMethod:但是想让特定的选择器通过转发机制被转发,你就对这些选择器返回NO。

动态加载

一个 Objective-C 程序在运行的时候,可以加载和链接新的类和分类。新的代码被合并到程序中,和一开始加载的类和分类进行相同的处理。

动态加载可以被用于做许多不同的事情。例如,系统偏好应用程序中的各个模块被动态加载。

在 Cocoa 环境,动态加载通常用于允许应用去自定义。其它则写在运行时加载的模块–就像接口构建器加载自定义的调色板和OS X系统首选项应用程序加载自定义的偏好模块一样。可加载的模块拓展了应用程序的功能。他们以你允许的方式出现,但你并没有预期或自定义。你提供了框架,但其他人提供了代码。
虽然这是一个运行时函数,使用Mach-O 文件( objc_loadModules, 定义在objc/objc-load.h)执行动态加载 Objective- C模块。Cocoa’s NSBundle 类提供了一个更为方便的接口为动态加载–它是面向对象的,并与相关服务集成。在 Foundation framework reference 里看 NSBundle 类的规范,包含NSBundle类的信息和它的使用。有关 Mach-O 文件的信息,请参考OS X ABI Mach - O 文件格式引用。

消息转发

向不处理该消息的对象发送消息是错误的。然而,通知错误之前,运行时系统给接收对象第二次机会去处理消息。

转发

如果你发送一个消息给一个不能处理这个消息的对象,在错误发生之前,运行时向对象发送一个带有NSInvocaiton对象(作为它的唯一的参数)的一个forwardInvocation:消息。NSInvocaiton 对象封装原始消息和传递的参数。

你可以实现一个forwardInvocation:方法为消息提供一个默认响应,或用一些其他的方式避免错误。如名字所暗示的,forwardInvocation: 通常被用于转发消息给另一个对象。

为了看转发的范围和意图,想象以下场景:假设,首先,你正在设计一个对象,他可以响应称之为 negotiate 的消息。你需要它的响应包含另一种对象的响应。你可以通过在 negotiate 的方法实现里传递一个 negotiate 消息给另一个对象来很容易的完成这个实现。

进一步的,你想让你的对象对 negotiate 消息的响应实际上在另一个类被实现。实现这一点的一种方式是使你的类继承另一个类的方法。然而,这种方式可能不可行。比如你的类和实现 negotiate 的类在继承层次的不同分支中。

虽然你的类不能继承 negotiate 方法,你仍然能够“借用”它。通过实现方法的一个版本(仅仅传递消息给另一个类的实例):

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

第二种选择是通过提供的 forwardInvocation: 消息,为这个问题提供了一个小的专设的方案,它是动态的而非静态的。它的工作方式:当一个对象因为它没有一个方法匹配消息里的选择器而不能响应一个消息时,运行时系统通过发送给它一个 forwardInvocation:消息通知对象。每一个对象从 NSObject 类继承一个 forwardInvocaition: 方法,然而,NSObject 的方法版本仅仅调用了doesNotRecognizeSelector:。通过复写 NSObject’s 版本以及实现你自己的,你能够利用这个机会,forwardInvocation:消息提供将消息转发给其他对象。

转发一个消息,所有的forwardInvocation: 方法需要去做的是:

  • 决定消息发送到哪里
  • 用原始参数发送它

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

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

注意
一个 anInvocation 参数从哪里来呢,其实 forwardInvocation: 消息发送之前,Runtime系统会向对象发送 methodSignatureForSelector: 消息,并取到要响应方法的对象的方法签名生成 NSInvocation 对象,所以我们在重写 forwardInvocation: 方法的同时,也要重写 methodSignatureForSelector:方法,否则程序会崩溃。

被转发消息的返回值将返回给原发送方。返回值的所有类型都被可以被递送给发送方,包括 ids,structures,和双精度浮点数。

一个 forwardInvocation: 方法作为未识别消息的发布中心,分配它们给不同的接收者。或者它能作为转运站,发送所有的消息给相同的目的地。它能调动一个消息给另一个。或者仅仅是“吞掉”一些消息使之无响应或无错误。一个forwardInvocation: 方法也能联合多个消息为一个响应。虽然 forwardInvocation: 怎么做给到了实现者。然而,它为将对象链接到一个转发链提供了机会,为程序设计提供了可能性。

注解
forwardInvocation: 方法仅仅在名义上的接收方法不调用现有方法的情况下,才去处理消息。例如,你想让你的对象去转发 negotiate 消息给另一个对象,它没有一个属于它自己的 negotiate 方法,如果它有 negotiate 方法,消息绝不会被forwardInvocation: 转发。

对于更多的转发和调用的信息,看 Foundation framework reference 的 NSInvocation 类说明。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    return  [OtherObject instanceMethodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    OtherObject *otherObject = [[OtherObject alloc]init];
    if ([otherObject respondsToSelector:[anInvocation selector]]) {
        [anInvocation invokeWithTarget:otherObject];
    }else {
        [super forwardInvocation:anInvocation];
    }
}

转发和继承

转发模仿的继承,能够对Objective-C程序产生多继承的效果。 如图 5-1 所示,对象通过转发它来响应一个消息,转发它给到了另一个借用或‘继承’的方法实现的类里。
MessagingForward

在这个图示里,Warrior 类的实例转发了一个 negotiate 消息给 Diplomat 类的实例。Warrior 类将出现 negotiate 像 Diplomat 类一样。它好像响应了 negotiate 消息,实际上它确实有响应(虽然这是 Diplomat 做的工作)。

因此转发消息的对象从两个分支“继承”方法,它自己的分支和响应消息的分支。在上面的例子中,Warrior 似乎和自己的 superclass 一样继承了 Diplomat。

转发提供了你通常想从多继承中获得的大部分特性。然而,这两者之间有重要的不同:多继承联合了多个功能在一个对象,它倾向于大的,多层面的对象。转发,在另一方面,将不同的职责给了不同的对象。它将问题分解到比较小的对象。但是这些对象以对消息发送方透明的方式关联起来。

代理对象

转发不仅模仿多继承,它也使开发轻量级对象成为可能,用以代表或“覆盖”更多实质的对象。代理代表另一个对象并且将消息传递给它。

在 Objective-C 编程语言中讨论的 “Remote Messaging” 代理就是这样的代理。一个代理负责为 remote receiver 关联转发消息的细节,确保在连接中复制和检索参数值,等等。但是它并不会做其它的,它不复制远程对象的功能,只是简单地给远程对象一个本地地址,一个它能够在另一个应用里接收消息的地方。

其它类型的代理对象也是可能的。例如,你有一个管理很多数据的对象,可能它创建了一个复杂的图片或在磁盘上读取内容。设置这个对象可能非常耗时。你喜欢懒加载—当它真正需要或系统资源暂时闲置时。同时,对于这个对象,你需要至少一个占位符,以便应用程序中的函数的其它对象能正常工作。

在这种情况下,你可以先初步创建,不必是一个完整的对象,但是它是一个轻量级的代理,这个对象可以自己做一些事情,像回答关于数据的问题,但大部分时候它仅仅为更大的对象保留一个位置,当时间到来时,将消息转发给它。当代理的 forwardInvocation:方法首先接受一个发送给另一个对象的消息,它要确保对象已经存在或者如果不存在就创建它。对于一个较大对象的所有消息都要通过一个代理,这样,就程序的其余部分而言,代理和较大对象将是相同的。

转发和多继承

虽然转发模仿继承,NSObject 类从不混淆者两者,像 respondsToSelector: 和 isKindOfClass: 方法看上去仅仅在继承层级,绝不在转发链。例如,如果询问一个 Warrior 对象是否响应 negotiate 消息,

if ([warrior respondsToSelector:@selector(negotiate)]) {
        NSLog(@"warrior respond to selector negotiage - YES");
    }else {
        NSLog(@"warrior  respond to selector negotiage - NO");
    }
// Prints "respond to selector negotiage - NO"

回答是NO,虽然它能够接收 negotiate 消息没有报错并且能够响应它们,从某种意义上说,是转发它们给了 Diplomat。(参见 Figure 5-1.)

在一些情况下,NO 是一个正确的回答。但也可能不是。如果你用转发去创建一个代理对象或去拓展一个类的功能,转发机制应该像继承一样透明。如果你想让你的对象表现的像是他们真的继承了他们转发的对象的行为,你需要重新实现 respondToSelector:和 isKindOfClass: 方法来包含转发算法:

- (BooL)respondsToSelector:(SEL)aSelector
{
       if ([ super respondsToSelector:aSelector] )
           return YES;

       else {
                   /* 这里,测试是否一个aSelector消息能够转发另一个对象和
                        是否对象能响应它,如果能返回YES */
               }
          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:方法的调用。

注解
这是一个高级技术,只适用于没有其他解决方案的情况下。不要企图去作为继承的一个替换。如果你一定要使用这个技术,要确保你完全理解类所做的转发行为和你转发的类。

被提到的方法在 Foundation 框架的 NSObject 章节类规范中有描述。对于 invokeWithTarget: 方法,看 Foundation 框架的 NSInvocation 类规范。

类型编码

为辅助运行时系统,编译器为字符串中的每一个方法名、返回值和参数类型进行了编码。编码结构在上下文中也很有用,因此,可以用 @encode 编译器指令来使用。当给定一个类型说明时,@encode() 返回那个类型的一个字符串编码。类型可以是基本类型像 int,a pointer,a tagged structure 或 union,或a class name—任何类型,实际上,能够被作为一个 C sizeof()操字符的参数的类型都可以使用。

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

下表列出了类型编码。请注意,这些编码与你为了归档或分发一个编码对象有许多重叠。然而,这里列出的 codes 写代码的时候是不能用的,如果你写代码的时候用了这些codes,它们就不能够通过 @encode 生成了。(更多关于编码对象进行归档和分发的信息,查看NSCoder类)。

Table 6-1 Objective-C 类型编码
TypeEncodings-01
TypeEncodings-02

重要
Objective-C 不支持 long double 类型。@encode(long double) 返回 d,和 double 的编码类型相同。
勘误 :在Xcode_V9.2 & iOS 11.0 long double 返回 D,不同于 double 类型编码 d 。

一个数组的类型编码被放在一个方括号中;数组的元素数量是需要在括号中优先指定的,要在类型之前,例如,一个有12个指针类型元素的数组被编码为:

[ 12^f ]

结构体被放在花括号中,联合体被放在圆括号中。首先以列表的形式列出,后面是通过一个等号和编码序列的方式列出。如下:

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

会编码为这个样子:

{ example=@ * i }

同样的编码结果无论定义为类型名(Example)还是结构体标记符(example)都将传递给 @encode() 。结构体指针的编码包含与结构体字段相同的信息量:

^{example=@*i}

然而,另一个间接层移除了内部的类型规范:

^^ { example }

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

{ NSObject=# }

NSObject类只是声明了一个Class类型的isa。

注意尽管 @encode() 指令没有返回它们,当它们被用于在协议中声明方法时,运行时系统用在表 6-2 中列出的额外的编码。

Table 6-2 Objective-C method encodings
MethodEncodings

注解
这些方法的参数和返回值的解释曾经被用于分布式对象。《The Objective-C Programming Language》的Remote Message 章节里使用过。
const:参数或指针是一个常量
in: 仅仅作为输入参数,不能够被引用
inout:参数是输入参数和输出参数
out:只做为输出参数,被用于返回值
bycopy: 通过拷贝传递或返回一个对象的副本,而不是使用引用/NSDistantObject
byref: 使用一个引用对象(默认)
oneway:用于返回值为 void 的消息定义中,oneway 为异步,其消息预计不会立即返回。

参考:Objective-C 方法参数编码注解Objective-C关键字oneway

属性声明

当编译器遇到属性声明(see Declared Properties in The Objective-C Programming Language),它生成了与类、分类或协议相关的元数据。你能够根据类或协议的名字,使用相关的函数来访问这个元数据查找一个属性。以@encode 字符串的形式获取一个属性的类型,并将属性的 attributes 复制为一个 C 字符串数组。一个已声明的属性列表对于每一个类和协议都是可用的。

属性类型和函数

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

typedef struct objc_property *Property;
/// An opaque type that represents an Objective-C declared property.
typedef struct objc_property *objc_property_t;

你能够用 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 LenderNSObjet {
       float alone;
}

@property float alone;

@end

你可以使用以下方法获取属性列表:

id LenderClass = objc_getClass(“Lender”);
unsigned int outcount;
object_property_t *properties = class_copyPropertyList(LenderClass, &outCount);

你能够使用 property_getName 函数去发现一个属性名:

const char *property_getName(objc_property_t  _Nonnull  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 isRequireProperty, BOOL isInstanceProperty)

你能用 property_getAttributes 函数去发现一个携带属性名字和 @encode 类型的字符串。对于编码类型字符串的详情,看 Type Encodings;对于这个字符串的详情,看下面属性类型字符串属性特性描述用例两节内容。

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));

}
// Prints "alone     Tf,V_alone"

属性类型字符串

字符串以 T 开始后面跟着 @encode 和逗号,以 V 跟着支持实例变量(勘误:实际测试为 V 后面跟着带下划线的实例变量)的名称结束。在这些之间,属性由以下描述符指定,由逗号分隔:

Table 7-1 Declared property type encodings
PropertyTypeEncodings

属性特性描述用例

给了这些定义:

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

下面展示了属性声明用例和通过 property_getAttributes: 返回的字符串:

Property_getAttributes-01
Property_getAttributes-02

备注
上表中的 Property Description 部分与原文档不同,原文档久未更新,图中值为Xcode 9.2 中最新编译结果。
写此文时原文档标注的最后更新日期:2009-10-19。


参考:
Objective-C Runtime Programming Guide
Objective-C Runtime

译者简介

张飞龙:iOS开发者,微信公众号:LorneNote
图片名称


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值