本文翻譯自NSHipster 的文章Associated Objects 。
1
#import <objc/runtime.h>
Objective-C開發(fā)者在遇到上面這條“咒語”相關(guān)的一些東西時,會不自覺的變的非常謹慎。一個主要原因是:弄亂Objective-C運行時可能會改變整個實現(xiàn)結(jié)構(gòu),因為所有的代碼都是運行在它之上的。
一方面:<objc/runtime.h>
中的函數(shù)可以給應(yīng)用或者框架增加強大的新特性,這是通過其他方式不可能做到的。但另一方面:它會改變代碼的正常運行邏輯和所有與之交互的東西(通常伴隨著可怕的副作用)。
因而,這是我們認為進行這種魔鬼交易最大的恐懼點,下面來看一個NSHipster讀者問得最多的一個主題:associated objects。
Associated Objects(關(guān)聯(lián)對象)或者叫作關(guān)聯(lián)引用(Associative References),是作為Objective-C 2.0 運行時功能被引入到 Mac OS X 10.6 Snow Leopard(及iOS4)系統(tǒng)。與它相關(guān)在<objc/runtime.h>
中有3個C函數(shù),它們可以讓對象在運行時關(guān)聯(lián)任何值:
objc_setAssociatedObject
objc_getAssociatedObject
objc_removeAssociatedObjects
為什么這幾個方法很有用呢?因為開發(fā)者可以通過它們在分類中給已存在的類中添加自定義屬性 。
NSObject+AssociatedObject.h1
2
3
@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end
NSObject+AssociatedObject.m1
2
3
4
5
6
7
8
9
10
@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));
}
通常推薦key使用static char
類型——使用指針或許更好,key值是一個唯一的常量,并只在getters和setters方法內(nèi)部使用:
1
2
3
static char kAssociatedObjectKey;
objc_getAssociatedObject(self, &kAssociatedObjectKey);
然而,一個更簡單的方案是:直接使用選擇器(selector)。
因為SEL生成的時候就是一個唯一的常量,你可以使用 _cmd 作為objc_setAssociatedObject()的key。
—— Bill Bumgarner(@bbum) August28, 2009
關(guān)聯(lián)對象的特性被關(guān)聯(lián)到對象的值根據(jù)使用的objc_AssociationPolicy
類型不同表現(xiàn)出不同的特性:
Behavior
對應(yīng)的@property類型
描述
OBJC_ASSOCIATION_ASSIGN
@property (assign) 或 @property(unsafe_unretained)
給關(guān)聯(lián)對象指定若引用
OBJC_ASSOCIATION_RETAIN_NONATOMIC
@property (nonatomic, strong)
給關(guān)聯(lián)對象指定非原子的強引用
OBJC_ASSOCIATION_COPY_NONATOMIC
@property (nonatomic, copy)
給關(guān)聯(lián)對象指定非原子的copy特性
OBJC_ASSOCIATION_RETAIN
@property (atomic, strong)
給關(guān)聯(lián)對象指定原子的強引用
OBJC_ASSOCIATION_COPY
@property (atomic, copy)
給關(guān)聯(lián)對象指定原子copy特性
通過OBJC_ASSOCIATION_ASSIGN
分配的弱關(guān)聯(lián)對象并不是完全和weak
修飾符引用一樣(對象初始化與釋放時被置空),反而更像是unsafe_unretained
,所以你需要在訪問弱關(guān)聯(lián)對象時稍微注意一下。
根據(jù)WWDC2011,Session322 對對象釋放時間的描述,associated objects清除在對象生命周期中很晚才執(zhí)行,通過被NSObject -dealloc
方法調(diào)用的object_dispose()
函數(shù)完成。
移除關(guān)聯(lián)對象一個的方法是試圖在某個時刻調(diào)用objc_removeAssociatedObjects()
函數(shù)來移除關(guān)聯(lián)對象,然而,根據(jù)蘋果文檔 描述,你不大可能有需求要自己去調(diào)用:
這個函數(shù)的主要目的是很容易的讓對象恢復(fù)成它“原始狀態(tài)”,你不應(yīng)該使用它來移除關(guān)聯(lián)的對象,因為它也會移除掉包括其他地方加入的全部的關(guān)聯(lián)對象。所以一般你只需要通過調(diào)用objc_setAssociatedObject
并傳入nil值來清除關(guān)聯(lián)值。
模式
添加私有變量來幫助實現(xiàn)細節(jié) 。當拓展一個內(nèi)置類時,可能有必要跟蹤一些額外的狀態(tài),這是關(guān)聯(lián)對象最普遍的應(yīng)用場景。例如:AFNetworking中在UIImageView
的分類中使用關(guān)聯(lián)對象來存儲一個請求操作對象(operation object),用于異步的從遠程獲取圖片。
添加公共屬性來設(shè)置分類的特性 。有時候,通過添加一個屬性讓一個分類更加靈活,而不是作為函數(shù)參數(shù)。這種情況下,使用關(guān)聯(lián)對象作為一個公開的屬性是可接受的解決方案。還是拿前面AFNetworking的例子來說,UIImageView
的分類中imageResponseSerializer
屬性允許圖片視圖隨意的使用一個過濾器,或者在圖片請求并緩存之前就可以修改它的渲染。
為KVO創(chuàng)建一個關(guān)聯(lián)的觀察者(observer) 。當在一個分類中使用KVO 的時候,推薦使用一個自定義的關(guān)聯(lián)對象作為觀察者,而不是對象自己觀察自己。
反模式
在不必要的時候使用關(guān)聯(lián)對象 。使用視圖時一個常見的情況是通過數(shù)據(jù)模型或一些復(fù)合的值來創(chuàng)建一個便利的方法設(shè)置填充字段或?qū)傩?。如果這些值在后面不會再被使用到,最好就不要使用關(guān)聯(lián)對象了。
使用關(guān)聯(lián)對象來保存一個可以被推算出來的值 。例如,有人可能想通過關(guān)聯(lián)對象存儲UITableViewCell
上一個自定義accessoryView的引用,使用tableView:accessoryButtonTappedForRowWithIndexPath:
和 cellForRowAtIndexPath:
即可以達到要求。
使用關(guān)聯(lián)對象來代替X 。其中X代表下面的一些項:
關(guān)聯(lián)對象應(yīng)該被當做最后的手段來使用(不得不用時才用),而不是為了尋求一個解決方案就行(事實上,分類本身就不應(yīng)該是解決問題優(yōu)先選擇的工具)。
像一些巧妙的伎倆、hack手段或者是變通的解決方案,人們總是傾向于創(chuàng)造機會來使用他們——特別是剛剛接觸他們時。盡可能的在理解并領(lǐng)悟之后再做出正確的方案,避免自己陷入一知半解的尷尬處境。