「翻译」运行时之关联对象
头文件:
#import <objc/runtime.h>
Objective-C 开发者习惯于警惕运行时的东西,理由是运行时改变了运行在它上面代码的实际结构。
另一方面,<objc/runtime.h>
的功能就是为应用或框架增加更强大的新特性,是其他的方式无法
实现的。同时它也可能破坏原来代码的逻辑结构,一切与之可能进行的交互,都将有可怕的副作用。
给我们带来极大的惶恐,因此,我们称之为浮士德,也是NSHipster读者经常被所要求的科目之一:
关联的对象。关联的对象或关联的引用,他们原本是Objective-C的2.0运行时的新特性,在OS X
雪豹中介绍(可在iOS 4的)的功能。它在运行时键值允许对象为任意值,这个条目是在
<objc/ runtime.h>声明的,有以下三个C函数:
objc_setAssociatedObject
objc_getAssociatedObject
objc_removeAssociatedObjects
为什么会这样有用吗?它允许开发人员在分类中自定义属性,拓展的类,弥补Objective-C的这个缺陷。
NSObject+AssociatedObject.h
@interface NSObject (AssociatedObject) @property (nonatomic, strong) id associatedObject; @end
NSObject+AssociatedObject.m @implementation NSObject (AssociatedObject) @dynamic associatedObject; - (void)setAssociatedObject:(id)object { objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (id)associatedObject { return objc_getAssociatedObject(self, @selector(associatedObject)); }
人们通常的建议是使用静态字符,或者更好的是指针。这基本上保证一个任意的值是固定的、唯一的,
而且仅限于getter和setter方法中使用:
static char kAssociatedObjectKey; objc_getAssociatedObject(self, &kAssociatedObjectKey);
然而,一个更简单的解决方案存在:只使用一个SEL。
关联对象的行为
值可以根据由枚举类型objc_AssociationPolicy定义的行为相关联的物体上
Behavior | @property Equivalent | Description |
---|---|---|
OBJC_ASSOCIATION_ASSIGN | @property (assign) or @property (unsafe_unretained) | Specifies a weak reference to the associated object. |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | @property (nonatomic, strong) | Specifies a strong reference to the associated object, and that the association is not made atomically. |
OBJC_ASSOCIATION_COPY_NONATOMIC | @property (nonatomic, copy) | Specifies that the associated object is copied, and that the association is not made atomically. |
OBJC_ASSOCIATION_RETAIN | @property (atomic, strong) | Specifies a strong reference to the associated object, and that the association is made atomically. |
OBJC_ASSOCIATION_COPY | @property (atomic, copy) | Specifies that the associated object is copied, and that the association is made atomically. |
移除值
有人可能控制不住地各种诱惑,在他们某些关联的对象时调用objc_removeAssociatedObjects()。
但是,如文档中描述的,你基本没有将有机会亲自调用它: 此函数的主要目的是可以很容易将对象返回
到“原始状态”,你不应该使用这个函数从对象一般移除关联,因为它也消除了其他对象可能已添加到该
对象关联。通常情况下,你应该使用objc_setAssociatedObject 设置nil,以清除关联。
模式
- 添加私有变量,以方便实现细节。当延伸的内置类的行为时,记录附加状态可能是必要的。这是可以当做教科书的关联对象的例子。例如,AFNetworking在UIImageView分类中使用关联对象来存储请求操作的对象,用于异步获取一个远程、特定的URL上的图像。
- 添加公共属性来配置类的行为。有时,用属性可以使分类更加灵活,比方法参数也更有意义。在这种情况下,一个面向大众的属性是使用关联的对象是可以接受的。回到AFNetworking例子中,它的UIImageView分类的imageResponseSerializer,允许图像视图有选择地应用过滤 器,另一方面也可以在设置和缓存到磁盘前更改远程图像的渲染。
-
创建一个KVO的关联观察者。当在分类的实现中使用KVO时,通常推荐使用自定义的关联对象可以作为一个观察者,而不是观察的对象本身。
反模式
- 不需要值时存储关联的对象。一个常见的视图模式是创建填充基于模型对象或复合值的字段和属性的便捷方法。如果该值以后不需要回调,就不与该对象相关联,这是可以接受的,也是合理的。
- 当 值可以被推断出来时存储关联的对象。例如,一个人也许会存储一个自定义辅助视图的包含的UITableViewCell的引用,用于实现代码如 下:tableView:accessoryButtonTappedForRowWithIndexPath:,这时候可以通过调用 cellForRowAtIndexPath: 恢复。
- 在如下的情况使用关联的对象:
- 继承比组合更合理时的子类。
- 添加交互事件来响应的 "目标-动作" 。
- 目标-动作不起作用时的 手势识别。
- 行为可以委托给另一个对象的代理。
- 松耦合方式的跨系统通信事件的通知、通知中心。
相关联的对象应被看作是不得已的方法,而不是寻找问题的解决方案(说真的,类本身真的不应该在工具链的顶端开始)。 如同任何聪明的把戏,骇客攻击,或解决方法,一般都会积极寻求应用的场合,尤其是了解之后,这是人的一种自然的倾向 。尽你所能地理解和欣赏时,这是正确的解决方案,保存自己被轻蔑地问:“为什么要以神的名义”,然后你决定去解决。
相关代码: