Runtime中Associated Objects应用

Associated Objects

Mattt Thompson撰写、 Croath Liu翻译、 发布于2014年2月10日

import <objc/runtime.h>

Objective-C开发者应该小心谨慎地遵循这个危险咒语的各种准则。一个很好的原因的就是:混乱的运行时代码会改变运行在其架构之上的所有代码。

从利的角度来讲, <objc/runtime.h> 中的函数具有其他方式做不到的、能为应用和框架提供强大功能的能力。而从弊的角度来讲,它可能会会毁掉代码的sanity meter,一切代码和逻辑都可能被异常糟糕的副作用影响(terrifying side-effects)。

因此,我们怀着巨大的恐惧来思考这个与“魔鬼的交易”(Faustian bargain),一起来看看这个最多地被NSHipster读者们要求讲讲的主题之一:对象关联(associated objects)。


对象关联(或称为关联引用)本来是Objective-C 2.0运行时的一个特性,起始于OS X Snow Leopard和iOS 4。相关参考可以查看<objc/runtime.h> 中定义的以下三个允许你将任何键值在运行时关联到对象上的函数:

  • objc_setAssociatedObject
  • objc_getAssociatedObject
  • objc_removeAssociatedObjects

为什么我说这个很有用呢?因为这允许开发者对已经存在的类在扩展中添加自定义的属性,这几乎弥补了Objective-C最大的缺点。


NSObject+AssociatedObject.h

Objective-C
@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end

NSObject+AssociatedObject.m

Objective-C
@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));
}

通常推荐的做法是添加的属性最好是 static char 类型的,当然更推荐是指针型的。通常来说该属性应该是常量、唯一的、在适用范围内用getter和setter访问到:

Objective-C
static char kAssociatedObjectKey;

objc_getAssociatedObject(self, &kAssociatedObjectKey);

然而可以用更简单的方式实现:用selector。

Since SELs are guaranteed to be unique and constant, you can use _cmd as the key > for objc_setAssociatedObject(). #objective-c #snowleopard

— Bill Bumgarner (@bbum) August 28, 2009
关联对象的行为

<h2 id="关联对象的行为">关联对象的行为</h2>

<p>属性可以根据定义在枚举类型 <code>objc_AssociationPolicy</code> 上的行为被关联在对象上:</p>

Behavior @property Equivalent Description
OBJC_ASSOCIATION_ASSIGN @property (assign) @property (unsafe_unretained) 指定一个关联对象的弱引用
OBJC_ASSOCIATION_RETAIN_NONATOMIC @property (nonatomic, strong) 指定一个关联对象的强引用,不能被原子化使用。
OBJC_ASSOCIATION_COPY_NONATOMIC @property (nonatomic, copy) 指定一个关联对象的copy引用,不能被原子化使用。
OBJC_ASSOCIATION_RETAIN @property (atomic, strong) 指定一个关联对象的强引用,能被原子化使用
OBJC_ASSOCIATION_COPY @property (atomic, copy) 指定一个关联对象的copy引用,能被原子化使用

OBJC_ASSOCIATION_ASSIGN</code> 类型关联在对象上的弱引用不代表0 retian的 <code>weak</code> 弱引用,行为上更像 <code>unsafe_unretained</code> 属性,所以当在你的视线中调用weak的关联对象时要相当小心。</p>

<blockquote>
<p>根据<a href="https://developer.apple.com/videos/wwdc/2011/#322-video">WWDC 2011, Session 322</a> (第36分钟左右)发布的内存销毁时间表,被关联的对象在生命周期内要比对象本身释放的晚很多。它们会在被 <code>NSObject -dealloc</code> 调用的 <code>object_dispose()</code> 方法中释放。</p>
</blockquote>

<h2 id="删除属性">删除属性</h2>

<p>你可以会在刚开始接触对象关联时想要尝试去调用 <code>objc_removeAssociatedObjects()</code> 来进行删除操作,但<a href="https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html#//apple_ref/c/func/objc_removeAssociatedObjects">如文档中所述</a>,你不应该自己手动调用这个函数:</p>

<blockquote>
<p>此函数的主要目的是在“初试状态”时方便地返回一个对象。你不应该用这个函数来删除对象的属性,因为可能会导致其他客户对其添加的属性也被移除了。规范的方法是:调用 <code>objc_setAssociatedObject</code> 方法并传入一个 <code>nil</code> 值来清除一个关联。</p>
</blockquote>

<h2 id="优秀样例">优秀样例</h2>

<ul>
<li><strong>添加私有属性用于更好地去实现细节。</strong>当扩展一个内建类的行为时,保持附加属性的状态可能非常必要。注意以下说的是一种非常<em>教科书式</em>的关联对象的用例:AFNetworking在 <code>UIImageView</code> 的category上用了关联对象来<a href="https://github.com/AFNetworking/AFNetworking/blob/2.1.0/UIKit%2BAFNetworking/UIImageView%2BAFNetworking.m#L57-L63">保持一个operation对象</a>,用于从网络上某URL异步地获取一张图片。</li>
<li><strong>添加public属性来增强category的功能。</strong>有些情况下这种(通过关联对象)让category行为更灵活的做法比在用一个带变量的方法来实现更有意义。在这些情况下,可以用关联对象实现一个一个对外开放的属性。回到上个AFNetworking的例子中的 <code>UIImageView</code> category,<a href="https://github.com/AFNetworking/AFNetworking/blob/2.1.0/UIKit%2BAFNetworking/UIImageView%2BAFNetworking.h#L60-L65">它的 <code>imageResponseSerializer</code></a>方法允许图片通过一个滤镜来显示、或在缓存到硬盘之前改变图片的内容。</li>
<li><strong>创建一个用于KVO的关联观察者。</strong>当在一个category的实现中使用<a href="http://nshipster.com/key-value-observing/">KVO</a>时,建议用一个自定义的关联对象而不是该对象本身作观察者。ng an associated observer for KVO**. When using <a href="http://nshipster.com/key-value-observing/">KVO</a> in a category implementation, it is recommended that a custom associated-object be used as an observer, rather than the object observing itself.</li>
</ul>

<h2 id="反例">反例</h2>

<ul>
<li><strong>当值不需要的时候建立一个关联对象。</strong>一个常见的例子就是在view上创建一个方便的方法去保存来自model的属性、值或者其他混合的数据。如果那个数据在之后根本用不到,那么这种方法虽然是没什么问题的,但用关联到对象的做法并不可取。</li>
<li><strong>当一个值可以被其他值推算出时建立一个关联对象。</strong>例如:在调用 <code>cellForRowAtIndexPath:</code> 时存储一个指向view的 <code>UITableViewCell</code> 中accessory view的引用,用于在 <code>tableView:accessoryButtonTappedForRowWithIndexPath:</code> 中使用。</li>
<li><strong>用关联对象替代X</strong>,这里的X可以代表下列含义:

<ul>
<li>当继承比扩展原有的类更方便时用<a href="https://developer.apple.com/library/ios/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html">子类化</a>。</li>
<li>为事件的响应者添加<a href="https://developer.apple.com/library/ios/documentation/general/conceptual/Devpedia-CocoaApp/TargetAction.html">响应动作</a>。</li>
<li>当响应动作不方便使用时使用的<a href="https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/GestureRecognizer_basics/GestureRecognizer_basics.html">手势动作捕捉</a>。</li>
<li>行为可以在其他对象中被代理实现时要用<a href="https://developer.apple.com/library/ios/documentation/general/conceptual/DevPedia-CocoaCore/Delegation.html">代理(delegate)</a>。</li>
<li>用<a href="http://nshipster.com/nsnotification-and-nsnotificationcenter/">NSNotification 和 NSNotificationCenter</a>进行松耦合化的跨系统的事件通知。


比起其他解决问题的方法,关联对象应该被视为最后的选择(事实上category也不应该作为首选方法)。
和其他精巧的trick、hack、workaround一样,一般人都会在刚学习完之后乐于寻找场景去使用一下。尽你所能去理解和欣赏它在正确使用时它所发挥的作用,同时当你选择<em>这个</em>解决办法时,也要避免当被轻蔑地问起“这是个什么玩意?”时的尴尬。</p>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容