Objective-C Runtime Programming Guide

2,444 阅读22分钟

Objective-C Runtime Programming Guide

Runtime Versions and Platforms

Objective C Runtime有两个不同的版本

Legacy and Modern Versions

1.0 legacy版本 & 2.0 modern版本, modern版本相较于1.0更为健壮

  • legacy runtime中, 如果改变了某类实例对象变量的内存布局, 必须重新编译这个类
  • modern runtime中, 如果改变了某类实例对象变量的内存布局, 则不需要重新编译这个类
  • modern runtime中,支持成员变量的属性合成

Platforms

  • iPhone和OS X v10.5及其以后的64位程序都是用的是modern runtime
  • 其他(32位的应用程序)使用的是legacy runtime

Interacting with the Runtime

Objective-C程序在三个不同的层面与运行时系统进行交互

  • Objective-C源码
  • Foundation框架的NSObject类中定义的方法
  • 运行时方法的直接调用

Objective-C Source Code

在大部分情况下, 运行时都在背后自动进行工作, 我们只需要编写Objective C代码, 进行编译即可

当编译包含Objective-C类和方法的代码时,编译器会创建实现语言动态特性的数据结构和函数调用

  • 数据结构捕获在类和类别定义以及协议声明中找到的信息
  • 包括类和协议对象, 方法选择器, 对象成员变量, 以及从源代码中提取的其他信息

NSObject Methods

Cocoa中大多数对象都是NSObject类子类对象, 都继承了NSObject的方法, 所以其子类对象获得了基础的行为操作(方法), 然而, 在某些情况下, NSObject类仅定义了如何完成操作的模板(只定义了接口), 本身并没有提供实现的所有代码

例如NSObject类提供了一个对象方法description用于返回一个字符串, 字符串的内容是对类的描述, 主要用于GDB print-object调试命令, 来输出字符串, NSObject对该方法的实现不知道继承自NSObject的子类类包含什么内容,因此它返回一个带有对象名称和地址的字符串, 子类需复写重新实现已满足自己的打印输出, 例NSArray实例对象会打印所包含的元素对象

一些NSObject方法只是使用运行时系统以获取信息, 例如isKindOfClass:判断是否是某类或是某类的子类和isMemberOfClass:判断是否是某类的实例, respondsToSelector:判断对象能否接收特定的消息, conformsToProtocol:判断对象是否实现了指定的协议, methodForSelector:返回一个方法实现的地址

Runtime Functions

  • 运行时系统是一个动态分享库
  • 由目录/usr/include/objc头文件中的一组功能和数据结构组成的公共接口
  • 大多数函数/方法允许使用C语言去替代编写OC代码时, 编译器所做的操作
  • NSObject类提供了一些构建的基础功能, 使用运行时系统可以开发可扩展的运行时工具
  • 通常编写Objective C代码时不常用, 但有时也能提供很强大的功能

Messaging

The objc_msgSend Function

在Objective-C中,消息直到运行时才绑定到方法实现。编译器将表达式为[receiver message]这种的消息转换为objc_msgSend方法发送消息, objc_msgSend方法首要两个参数分别为方法的接收者receiver, 发送消息的方法selector

[receiver message] ====> objc_msgSend(receiver, selector)

多类型参数传递

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

消息传递功能完成了动态绑定所需的一切

  • 首先找到方法选择器(找到方法实现), 由于不同的类可以实现相同的方法, 所以还需要receiver接受者来查找确切的方法选择器
  • 然后执行查询例程, 向其传递接收对象(指向其数据的指针), 以及为该方法指定的选择器
  • 最后,将例程的返回值作为自己的返回值传递

注意 编译器生成对消息传递功能的调用, 编写代码的时候不应该直接调用

消息传递的关键在于编译器为每个类和对象构建的结构, 每个类的结构都包含这两个基本要素

  • 一个指向父类的指针
  • 一个类调度表(类的查询表), 表内存存储了类其选择器关联的方法实现地址, 例如setOrigin::方法的选择器就关联了setOrigin::方法的实现地址

一个对象创建了, 内存分配了, 实例变量初始化了, 对象中第一个变量是一个指向其类的指针, 名为isa的指针, 能够让对象访问到它的类, 然后就能访问到它所继承的所有类

虽然不是严格的语言组成部分,但isa指针是对象与Objective-C运行时系统一起使用的关键, 无论结构定义的何种字段, 对象都必须与结构objc_object(在objc/objc.h中定义)"等效", 不需要创建自己的根类, 只要创建NSObject, NSProxy类及其子类对象, 默认含有isa指针

Messaging Framework

当一个消息发送给一个对象后, 消息发送方法会根据对象的isa指针找到对象所属的类class, 在类的调度表中查找选择器, 没有找到的话,objc_msgSend就会根据类的superClass指针向上, 到父类中继续查找, 如果一直查找失败的话, objc_msgSend则会一直向上查找直到NSObject类, 一旦查找到选择器, 就执行方法的调用, 调用在表中selector方法,并将接收对象的数据结构传递给该方法

这就是在运行时选择方法实现的方式---在面向对象编程的术语中,方法是动态绑定到消息的

为了加速消息查找发送的过程, 运行时系统会缓存方法选择器, 方法地址, 每个类都有自己独立的缓存, 缓存能够包含来自继承类的选择器, 在执行搜索类调度表之前, 消息传递例程首先检查接收对象的类的缓存(基于曾经使用过的方法可能会再次使用), 如果方法在缓存中查到了, 消息传递仅比函数调用慢一点

一旦程序运行了足够长的时间以"warm up"其缓存,它发送的几乎所有消息都将找到一个缓存方法。缓存在程序运行时动态增长以容纳新的消息

Using Hidden Arguments

objc_msgSend方法在执行查询方法实现的过程中, 查找到实现后, 会把所有参数全部传递给方法, 它同样会传递两个隐藏的参数

  • 方法的接收者receiver
  • 方法选择器selector

这是所有方法实现都会传递的参数, 之所以说它们是"隐藏的", 是因为它们没有在定义方法的源代码中声明, 当代码进行编译的时候, 会自动插入这两个参数

虽然这两个参数没有显示声明, 在编写代码的时候仍然可以引用它们, 使用self引用消息的接收者receiver, 使用_cmd引用方法选择器selector

下面的例子中_cmd代表的就是方法strange, self代表的就是消息接收者对象, 接收strange消息

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

Getting a Method Address

规避动态绑定的唯一方法是获取方法的地址并像调用函数一样直接调用它, 这在少数情况下会很合适, 因为在这种情况下, 特定方法将连续执行多次, 并且希望避免每次执行该方法时消息传递的开销

使用NSObject类中的methodForSelector:方法可以查询一个指向方法实现的指针, 然后用指针执行方法

methodForSelector:方法返回的指针必须仔细转换为正确的函数类型, 返回和参数类型都应包含在强制类型转换中

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:规避动态绑定可节省消息传递所需的大部分时间, 只有在重复多次特定消息的情况下节省的开销才是可观的

注意 methodForSelector:由Cocoa的运行时系统提供, 但是它并不是Objective C语言的特性

Dynamic Method Resolution

Dynamic Method Resolution

某些情况下, 您可能希望动态提供方法的实现, 例如在Objective C语言中, 使用@dynamic声明的属性, 告诉编译器, 属性关联的方法(setter, getter)方法将自己实现

@dynamic propertyName;

可以使用resolveInstanceMethod:resolveClassMethod:动态地为实例方法和类方法的给定选择器提供实现

可以使用class_addMethod方法给类添加方法

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

@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

转发方法(如Message Forwarding中所述)和动态方法解析在很大程度上是正交的, 类有机会在转发机制启动之前动态解析方法, 如果调用responsesToSelector:或instanceRespondToSelector:,则动态方法解析器将有机会首先为选择器提供IMP, 如果实现resolveInstanceMethod:但希望通过转发机制实际转发特定的选择器则对这些选择器返回NO

动态方法解析示例

出处: Effective Objective C 2.0 - Matt Galloway

实现@dynamic属性


@interface EOCAutoDictionary : NSObject

@property (copy, nonatomic) NSString *string;

@property (strong, nonatomic) NSNumber *number;

@property (strong, nonatomic) NSDate *date;

@property (strong, nonatomic) id opaqueObject;

@end

#import "EOCAutoDictionary.h"
#import <objc/runtime.h>

@interface EOCAutoDictionary ()

@property (strong, nonatomic) NSMutableDictionary *backingStore;

@end

@implementation EOCAutoDictionary

@dynamic string, number, date, opaqueObject;

id autoDictionaryGetter(id self, SEL _cmd);
void autoDictionarySetter(id self, SEL _cmd, id value);

- (instancetype)init {
    if (self = [super init]) {
        _backingStore = NSMutableDictionary.new;
    }
    return self;
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
//    if (/* selector from dynamic property */) {
        NSString *selectorString = NSStringFromSelector(sel);
        if ([selectorString hasPrefix:@"set"]) {
            class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@");
        } else  {
            class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:");
        }
        return YES;
//    }
//    return [super resolveInstanceMethod:sel];
}

id autoDictionaryGetter(id self, SEL _cmd) {
    // Get the backing store from the object
    EOCAutoDictionary *typeSelf = (EOCAutoDictionary *)self;
    NSMutableDictionary *backingStore = typeSelf.backingStore;
    
    // The key is simply the selector name
    NSString *key = NSStringFromSelector(_cmd);
    
    // Return the value
    return [backingStore objectForKey:key];
}

void autoDictionarySetter(id self, SEL _cmd, id value) {
    // Get the backing store from the object
    EOCAutoDictionary *typeSelf = (EOCAutoDictionary *)self;
    NSMutableDictionary *backingStore = typeSelf.backingStore;
    
    NSString *selectorString = NSStringFromSelector(_cmd);
    NSMutableString *key = selectorString.mutableCopy;
    
    // Remove the ":" at the end
    [key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
    
    // Remove the 'set' prefix
    [key deleteCharactersInRange:NSMakeRange(0, 3)];
    
    // Lowercase the first character
    NSString *lowercaseFirstChar = [key substringToIndex:1].lowercaseString;
    [key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
    
    if (value) {
        [backingStore setObject:value forKey:key];
    } else {
        [backingStore removeObjectForKey:key];
    }
}

@end

Dynamic Loading

一个Objective C程序在运行的时候可以加载和链接新的类和分类, 新代码已合并到程序中, 并与开始时加载的类和分类完全相同

动态加载可以用来做很多不同的事情. 例如, 系统偏好设置应用程序中的各个模块是动态加载的

在Cocoa环境中, 动态加载通常用于允许自定义应用程序, 程序运行时阶段可以加载他人编写的模块, 就像Interface Builder加载自定义选项板和OS X System Preferences应用程序加载自定义首选项模块一样, 可加载模块扩展了应用程序的功能

尽管有一个运行时函数可以在Mach-O文件中执行Objective-C模块的动态加载(objc_loadModules,在objc/ objc-load.h中定义), 但Cocoa的NSBundle类为动态加载提供了明显更方便的接口, 更加面向对象和集成化

Message Forwarding

向不处理该消息的对象发送消息是错误的, 但是, 在返回错误之前, 运行时系统会给接收对象第二次处理消息的机会

Forwarding

如果将消息发送给不处理该消息的对象, 返回错误之前运行时向该对象发送一个forwardInvocation消息, 其中NSInvocation对象作为其唯一参数, NSInvocation对象封装了原始消息以及与之一起传递的参数

可以实现forwardInvocation:方法, 以对消息提供默认响应或以其他方式避免错误, 顾名思义,forwardInvocation:通常用于将消息转发到另一个对象

若要查看转发的范围和意图请设想以下情形:假设首先正在设计一个对象, 该对象可以响应称为协商negotiate的消息, 并且希望其响应包括另一种对象的响应, 可以简单实现在消息negotiate内部, 让其他对象来响应自身的negotiate消息, 进一步进行此操作, 并假希望对象对negotiate消息的响应正是在另一个类中实现的响应, 实现此目的的一种方法是使当前类从另一类继承该方法, 然而这种方式并不是合适的, 因为实现negotiate消息的类可能来自不同的继承树

即使当前的类无法继承negotiate方法, 仍然可以通过实现该方法的版本来"借用"该方法, 该方法将消息简单地传递到另一个类的实例

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

这种方式可能会有点麻烦, 尤其是一个对象向另一个对象传递大量的消息, 必须实现一个方法来涵盖想从另一类中借用的所有方法, 在编写代码时, 可能有不知道的case, 会造成覆盖不全, 该集合可能取决于运行时的事件, 并且可能随着将来实现新方法和类而改变

相较于静态方法, 动态方法forwardInvocation:消息提供的第二次机会提供了针对此问题的解决方案, 它的工作方式如下:当对象由于没有与消息中的选择器匹配的方法而无法响应消息时,运行时系统会通过向它发送forwardInvocation:消息来通知对象, 每个NSObject对象都继承了forwardInvocation:方法, 如果未实现此方法, 未经历消息转发过程, 最终则会调用doesNotRecognizeSelector:抛出异常, 通过覆盖NSObject的版本并实现自己的版本, 可以利用forwardInvocation:消息提供的将消息转发到其他对象的机会

复写forwardInvocation:方法要注意两点

  • 确定消息应该去哪里
  • 将其原始参数发送到新的去处

消息可以通过方法invokeWithTarget:发送

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

最终消息的返回值会返回到初始的消息发送者那里, 所有类型的返回值都可以传递给发送者, 包括id类型的对象, 结构和双精度浮点数等

forwardInvocation:方法可以充当未识别消息的分发中心, 将其打包到其他接收者, 也可以是将所有消息发送到同一目的地的中转站, 可以将消息转化为另一种消息, 又或是将消息内化, 以至于没有响应和没有错误, forwardInvocation:方法还可以将多个消息合并为一个响应, 它提供了在转发链中链接对象的机会, 为程序设计开辟了可能性

注意只有当receiver没有实现响应消息选择器, 才会执行forwardInvocation:的处理,

Forwarding and Multiple Inheritance

转发和多重继承

转发可以模拟继承, 可实现多重继承的某些效果

Forwarding

在上图中,Warrior类的实例将negotiate消息转发到Diplomat类的实例, 战士看起来像外交官一样进行谈判, 它似乎对谈判的消息做出了回应, 并且出于所有实际目的, 它的确做出了回应(尽管实际上是外交官在从事这项工作)

因此, 转发消息的对象从继承层次结构的两个分支(其自己的分支以及响应消息的对象的分支)"继承"了方法, 在上面的示例中, Warrior类似乎继承自Diplomat及其自己的父类

转发提供了通常需要多重继承的大多数功能, 但是, 两者之间有一个重要的区别:多重继承是在单个对象中结合了不同的功能, 它倾向于大型, 多面的物体, 而转发将不同的职责分配给不同的对象, 它将问题分解为较小的对象, 以消息的形式发送给这些对象

Surrogate Objects

转发不仅模拟多重继承, 可以创建更为轻量级的对象响应更多对象方法, 代理另一个对象,并向其发送消息

The Objective-C Programming Language Remote Messaging中讨论的proxy就是这样的代理, proxy负责管理将消息转发到远程接收者的所有细节, 确保连接和复制参数值等等, 此外就不做其他过多的事情, 它不能复制远程对象的功能, 只是给远程对象一个本地地址, 一个可以在另一个应用程序中接收消息的地址

其他种类的替代对象也是可能的, 例如, 有一个可以处理大量数据的对象, 它也许会创建一个复杂的图像或读取磁盘上文件的内容, 设置该对象可能很耗时, 因此确实需要时或系统资源暂时空闲时进行懒加载操作, 同时, 该对象至少需要一个占位符, 以使应用程序中的其他对象正常运行

在这种情况下, 一开始可以不用创建的完整的对象, 而是轻量级的替代对象, 当任务发生的时候, 将消息传递给这个对象, 当代理对象的forwardInvocation:方法首先收到发往另一个对象的消息时, 它将确保该对象存在, 如果不存在则将创建该对象, 较大对象的所有消息都通过代理, 因此就程序的其余部分而言, 代理和较大对象的作用将是相同的

Forwarding and Inheritance

尽管转发模仿继承, 但NSObject类从不会混淆两者, 诸如responsToSelector:isKindOfClass:之类的方法仅查看继承层次结构, 例如, 询问Warrior对象是否响应negotiate消息

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

Warrior实例对象虽然将消息negotiate转发给了Diplomat实例对象, 没有错误并且响应了消息, 但[aWarrior respondsToSelector:@selector(negotiate)]结果是NO, Warrior类中的派发表中并没有negotiate选择器的地址

在许多情况下, 否(NO)是正确的答案. 但事实并非全都如此, 如果使用转发来设置代理对象或扩展类的功能, 则转发机制应该与继承一样透明, 如果希望目标对象像它们真正继承了转发消息的对象的行为一样工作, 则需要重新实现responsToSelector:isKindOfClass:方法以包括转发算法

- (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;
}

除了responsesToSelector:isKindOfClass:外, instancesRespondToSelector:方法也要针对消息转发做相应的修改, 如果还遵循的有协议, conformsToProtocol:方法也是一样的要做处理

如果对象转发了它收到的任何远程消息, 则它应该具有methodSignatureForSelector的版本, 该版本可以返回对最终响应所转发消息的方法的准确描述, 实现forwardInvocation:方法前一定要先实现methodSignatureForSelector:方法, 方法返回了NSMethodSignature类的对象才会进入forwardInvocation:方法实现

例如, 如果对象能够将消息转发到其代理则可以像如下实现methodSignatureForSelector:方法

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

注意 消息转发是一项先进的技术, 仅适用于无法解决其他问题的情况, 它的目标也不是打算来取代继承, 如果必须使用到此技术, 请确保完全了解进行转发的类和要转发到的类的行为

实例方法转发流程


// 未查找到选择器对应方法地址时, 给一次动态添加方法的机会
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;
}

// resolveInstanceMethod返回NO了进入, 改变方法的接收者
- (id)forwardingTargetForSelector:(SEL)aSelector {
    return nil;
}

// forwardingTargetForSelector也返回nil, 则进入完整的消息转发 获取转发对象响应方法的签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    return nil;
}

// methodSignatureForSelector返回有值进入forwardInvocation
/**
 可以改变消息的接收者target -- `NSInvocation - (void)invokeWithTarget:(id)target`
 或者改变响应方法的参数 -- `NSInvocation - (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx`
 改变返回值 -- `NSInvocation - (void)setReturnValue:(void *)retLoc`
 也可以实现其他`anInvocation`或者不响应, 总之自由度比较高
*/
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"target: %@, selector: %@", anInvocation.target, NSStringFromSelector(anInvocation.selector));
}

// 没有进入消息转发流程methodSignatureForSelector方法返回nil则进入, 然后抛出异常
- (void)doesNotRecognizeSelector:(SEL)aSelector {
    
}

类方法转发流程

+ (BOOL)resolveClassMethod:(SEL)sel {
    return NO;
}

+ (id)forwardingTargetForSelector:(SEL)aSelector {
    return nil;
}

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

+ (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"target: %@, selector: %@", anInvocation.target, NSStringFromSelector(anInvocation.selector));
}

+ (void)doesNotRecognizeSelector:(SEL)aSelector {
    
}

Type Encodings

为了帮助运行时系统, 编译器将每个方法的返回和参数类型编码为字符串, 并将该字符串与方法选择器关联

它使用的编码方案在其他上下文中也很有用,因此可以通过@encode()编译器指令直接使用, 在给定类型说明后,@encode()返回对该类型进行编码的字符串, 该类型可以是基本类型例如int, 指针, 带标签的结构体或联合体或类名-实际上可以做用于C语言sizeof()运算符的参数的任何类型

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

下表列出了类型编码, 注意它们中的许多编码发生在对象进行归档或分发, 但是一下列出的类型编码在自行编写编码器的时候并不能直接使用

Objective-C type encodings

Code Meaning
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
bnum A bit field of num bits
^type A pointer to type
? An unknown type (among other things, this code is used for function pointers)

重要 Objective-C不支持long double类型, @encode(long double)返回d, 它与double的编码相同

数组的类型使用方括号表示, 举例包含12个浮点数的指针的数组类型编码表示如下

[12^f]

结构体使用大括号表示, 例如结构体Example

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

结构体Example的类型编码为(@encode()后)

{example=@*i}

Example结构体指针的类型编码则为

^{example=@*i}

但是,另一种间​​接访问级别删除了内部类型规范

^^{example}

NSObject类的类型编码, 对象被当作结构对待, NSObject类仅声明一个Class类型的实例变量isa

{NSObject=#}

在协议申明声明的方法中, 运行时系统会用到特定的类型编码, 如下

Objective-C method encodings

Code Meaning
r const
n in
N inout
o out
O bycopy
R byref
V oneway

NSInvocation.h中的枚举

enum _NSObjCValueType {
    NSObjCNoType = 0,
    NSObjCVoidType = 'v',
    NSObjCCharType = 'c',
    NSObjCShortType = 's',
    NSObjCLongType = 'l',
    NSObjCLonglongType = 'q',
    NSObjCFloatType = 'f',
    NSObjCDoubleType = 'd',
    NSObjCBoolType = 'B',
    NSObjCSelectorType = ':',
    NSObjCObjectType = '@',
    NSObjCStructType = '{',
    NSObjCPointerType = '^',
    NSObjCStringType = '*',
    NSObjCArrayType = '[',
    NSObjCUnionType = '(',
    NSObjCBitfield = 'b'
} API_DEPRECATED("Not supported", macos(10.0,10.5), ios(2.0,2.0), watchos(2.0,2.0), tvos(9.0,9.0));

Declared Properties

当编译器遇到属性声明时(详情查看The Objective-C Programming Language Table of Contents中的Declared Properties部分), 编译器会生成与封闭类、分类或协议关联的描述性元数据, 可以使用函数访问此元数据,通过将属性类型作为@encode字符串以及将属性的属性列表复制为C字符串数组, 这些函数可以做到支持按类或协议上的名称查找属性, 每个类和协议都有声明的属性列表

Property Type and Functions

属性结构体定义属性描述符的不透明句柄

typedef struct objc_property *Property;

获取类和协议的属性列表

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

可以获取属性名称

const char *property_getName(objc_property_t 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)

查询属性@encode的类型编码字符串

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方法可以查询属性的名称, @encode类型编码字符串, 以及属性的其他属性

字符串以T开头后跟@encode类型和逗号,最后以V开头然后是实例变量的名称. 属性由以下指定描述符组成用逗号分隔

Declared property type encodings

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 Examples

特定用例定义

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

通过property_getAttributes:获取属性查询的信息样例

Property declaration Property description
@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

理解如有错误 望指正 转载请说明出处