#前言
前不久剛寫了 談Objective-C類成員變量 髓需,分析了成員變量的實現(xiàn)原理以及不能動態(tài)添加的原因奕枝,在這篇文章里我們來根據(jù) objc4-646.tar.gz版本 源碼來談一下 Objective-C 關聯(lián)對象的實現(xiàn)原理。
關聯(lián)對象(Associated Objects)是 Objective-C 2.0運行時的一個特性齿尽,起始于OS X Snow Leopard和iOS 4锌介。它允許開發(fā)者對已經(jīng)存在的類在擴展中添加自定義的屬性令哟。相關參考可以查看 <objc/runtime.h>
中定義的三個允許你將任何鍵值在運行時關聯(lián)到對象上的函數(shù):
- void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) 用于給對象添加關聯(lián)屬性心傀,傳入nil則移除已有的關聯(lián)對象
- id objc_getAssociatedObject(id object, const void *key) 用于獲取關聯(lián)屬性
- void objc_removeAssociatedObjects(id object) 移除一個對象所有的關聯(lián)屬性屈暗,但不建議手動調(diào)用這個函數(shù),因為這可能會導致其它人對其添加的屬性也被移除了。你可以調(diào)用
objc_setAssociatedObject
方法并傳入nil來指定移除某個關聯(lián)
下面分析一下 objc_setAssociatedObject
兩個參數(shù) key
和 policy
#key
通常來說該屬性應該是常量养叛、唯一的种呐,在getter和setter方法中都可以訪問到。這里有兩種常見的添加方式:
第一種是添加 static char 類型的變量一铅,當然更推薦是指針型的陕贮。
static char kAssociatedObjectKey;
- (void)setMenber:(NSString *)menber {
objc_setAssociatedObject(self, &kAssociatedObjectKey, menber, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
當然更推薦的是使用更簡單的方式實現(xiàn):用 selector(getter方法):
- (void)setMenber:(NSString *)menber {
objc_setAssociatedObject(self, @selector(menber), menber, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
#關聯(lián)策略 policy
關聯(lián)策略跟屬性修飾符的使用方法差不多堕油,屬性可以根據(jù)定義在 objc_AssociationPolicy 上的類型被關聯(lián)到對象上:
關聯(lián)策略 | 等價屬性 | 說明 |
---|---|---|
OBJC_ASSOCIATION_ASSIGN | @property (assign)或 @property (unsafe_unretained) | 弱引用關聯(lián)對象 |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | @property (nonatomic, strong) | 強引用關聯(lián)對象潘飘,且為非原子操作 |
OBJC_ASSOCIATION_COPY_NONATOMIC | @property (nonatomic, copy) | 復制關聯(lián)對象,且為非原子操作 |
OBJC_ASSOCIATION_RETAIN | @property (atomic, strong) | 強引用關聯(lián)對象掉缺,且為原子操作 |
OBJC_ASSOCIATION_COPY | @property (atomic, copy) | 復制關聯(lián)對象卜录,且為原子操作 |
#關聯(lián)對象實現(xiàn)
下面讓我們具體來分析一下這幾個函數(shù)的具體實現(xiàn)吧!
分析objc_setAssociatedObject實現(xiàn)
在objc_setAssociatedObject
的實現(xiàn)被定義在objc-auto.mm
文件 467 行
GC_RESOLVER(objc_setAssociatedObject)
#define GC_RESOLVER(name) \
OBJC_EXPORT void *name##_resolver(void) __asm__("_" #name); \
void *name##_resolver(void) \
{ \
__asm__(".symbol_resolver _" #name); \
if (UseGC) return (void*)name##_gc; \
else return (void*)name##_non_gc; \
}
- ## 符號: 連接宏眶明。舉個例子:
#define COMMAND(A, B) A##B
艰毒, int COMMAND(temp, Int) = 10 等同于 int tempInt = 10 - UseGC 是否使用垃圾回收,在 iPhone 平臺上被定義為 NO
所以這個宏展開來為下面的代碼
void GC_RESOLVER(name)
{
return (void*)objc_setAssociatedObject_non_gc();
}
objc_setAssociatedObject_non_gc
的實現(xiàn)在objc-runtime.m
文件搜囱,再經(jīng)過一些跳轉丑瞧,可以發(fā)現(xiàn) objc_setAssociatedObject 最終會調(diào)用 _object_set_associative_reference
方法 (objc-runtime.m 268行)
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// break any existing association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
} else {
// setting the association to nil breaks the association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
if (old_association.hasValue()) ReleaseValue()(old_association);
}
-
AssociationsManager manager;
, 會創(chuàng)建一個AssociationsManager
結構體的變量 manager,在調(diào)用它的構造函數(shù)時會上鎖蜀肘,調(diào)用析構函數(shù)時解鎖绊汹。結構體內(nèi)有一個靜態(tài)變量AssociationsHashMap
, 懶加載該變量。 - DISGUISE(object) 用來獲取 object 的指針地址
-
AssociationsHashMap
是一個無序的哈希表扮宠,維護了從對象地址到 ObjectAssociationMap 的映射 - ObjectAssociationMap 是一個map西乖,維護了從 key 到 ObjcAssociation 的映射
- ObjcAssociation 是一個 C++ 類, 主要包括兩個成員變量:uintptr_t _policy(關聯(lián)策略) id _value(關聯(lián)對象的值)
簡單的講解上面那個函數(shù)的流程:
- 新建一個 AssociationsManager 實例 manager坛增,同時上鎖获雕。通過 manager 得到 AssociationsHashMap 關聯(lián)哈希表 associations,通過 DISGUISE()函數(shù)得到 object 的指針 disguised_object收捣。在哈希表 associations 中 根據(jù) disguised_object 查找 ObjectAssociationMap届案,如果沒有則新建一個 refs。
- 新建一個 ObjcAssociation 實例 new_association罢艾,存儲在 refs 中
- 如果傳入的value是nil萝玷,則在 refs 移除該映射關系
- 釋放掉舊的 old_association
- 作用域結束釋放掉 manager,解鎖
分析objc_getAssociatedObject實現(xiàn)
按照上一節(jié)的流程昆婿,我們首先找到 objc_getAssociatedObject 的最終實現(xiàn)源碼:
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
}
return value;
}
代碼量比上一節(jié)少了還挺多哈球碉,過程也類似,就不講的很細了
- 先得到 AssociationsHashMap 實例 associations(靜態(tài)變量)仓蛆。根據(jù) object 的指針地址睁冬,在 associations 得到映射的 ObjectAssociationMap refs。
- 在 refs 根據(jù) key 得到映射的 ObjcAssociation 實例 entry,在 entry 中可以得到成員變量 _value豆拨,也就是我們所關聯(lián)屬性的值直奋。
- 根據(jù)關聯(lián)策略 policy 進行相應的操作(autorelease, retain)后返回 value
分析objc_removeAssociatedObjects實現(xiàn)
void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (associations.size() == 0) return;
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
for_each(elements.begin(), elements.end(), ReleaseValue());
}
其實不看代碼應該也能夠猜出個大概了吧.
- 根據(jù) object地址 找到映射的 refs,遍歷 refs施禾,將保存著的 value 保存在
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements
- 刪除 refs脚线, 然后一個個的釋放 elements 里面的值
#給類對象關聯(lián)對象
看完源代碼后,我們知道實例對象地址與 ObjectAssociationMap map是一一對應的弥搞。那么是否可以給類對象添加關聯(lián)對象呢邮绿?
答案是可以,因為Class也是一個對象攀例,我們完全可以用同樣的方式給類對象添加關聯(lián)對象船逮,只不過我們一般情況下不會這樣做,因為更多時候可以通過 static 變量來實現(xiàn)類級別的變量粤铭。
你可以通過下面的代碼這樣操作
@implementation NSObject (AssociatedObject)
+ (NSString *)associatedObject {
return objc_getAssociatedObject(self, @selector(associatedObject));
}
+ (void)setAssociatedObject:(NSString *)associatedObject {
objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
- (void) foo {
NSObject.associatedObject = @"associatedObject";
}
#何時釋放關聯(lián)對象
在 探究ARC下dealloc實現(xiàn) 中我們研究過挖胃,當對象引用計數(shù)變?yōu)?時會調(diào)用 dealloc 方法,然后最終調(diào)用 objc_destructInstance
方法來執(zhí)行釋放所有__weak
修飾的指向該對象的指針梆惯,釋放關聯(lián)對象酱鸭,釋放該對象成員變量的操作
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = !UseGC && obj->hasAssociatedObjects();
bool dealloc = !UseGC;
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
if (dealloc) obj->clearDeallocating();
}
return obj;
}
void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (associations.size() == 0) return;
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
for_each(elements.begin(), elements.end(), ReleaseValue());
}
是不是有點熟悉呢,在上上節(jié)中我們剛剛分析過這個方法垛吗。當對象 dealloc 時凹髓,會自動調(diào)用 objc_removeAssociatedObjects
方法來釋放所有的關聯(lián)對象。
#總結一下
- 類實例跟關聯(lián)對象(關聯(lián)的屬性)并沒有直接的存儲關系职烧,關聯(lián)對象在創(chuàng)建時后存儲在一個靜態(tài)哈希表中扁誓,根據(jù)類實例的指針映射到該關聯(lián)對象
- 當類實例 dealloc 后,會從哈希表中釋放該實例的所有的關聯(lián)對象
- 關聯(lián)對象的關聯(lián)策略跟屬性的修飾符非常的相似蚀之,要合理使用避免 crash
- 比起其他解決問題的方法蝗敢,關聯(lián)對象應該被視為最后的選擇