簡介
Associated Objects
是 Objective-C 2.0
中 Runtime
的特性之一。
在 <objc/runtime.h>
中定義的三個方法,
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy); id objc_getAssociatedObject(id object, const void *key); void objc_removeAssociatedObjects(id object);
從上面可以看出,
objc_setAssociatedObject
用於給對象添加關聯對象,傳入 nil
則可以移除已有的關聯對象;
objc_getAssociatedObject
用於獲取關聯對象;
objc_removeAssociatedObjects
用於移除一個對象的所有關聯對象。
object
:傳入關聯對象的所屬對象,也就是增加成員的實例對象,一般來說傳入 self
。
key
:唯一標記,即可以使用 static char
作爲 key
值,也可以使用 static void *kAssociatedObjectKey
指針作爲 key
值,當然推薦使用 用 selector
,使用 getter
方法的名稱作爲 key
值,可以利用 _cmd
來方便的取出 selector
。
value
:傳入關聯對象。
policy
:objc_AssociationPolicy
是一個 Objective-C
枚舉類型,也代表關聯策略。
注意:objc_removeAssociatedObjects這個方法會移除一個對象的所有關聯對象,一般通過給 objc_setAssociatedObject
函數傳入 nil
來移除某個已有的關聯對象。
關聯策略
OBJC_ASSOCIATION_ASSIGN
:弱引用關聯對象,一般修飾詞爲 assign
、unsafe_unretained
。
OBJC_ASSOCIATION_RETAIN_NONATOMIC
:強引用關聯對象,非原子操作,修飾詞爲 strong
、nonatomic
。
OBJC_ASSOCIATION_COPY_NONATOMIC
:複製關聯對象,非原子操作,修飾詞爲 copy
、nonatomic
。
OBJC_ASSOCIATION_RETAIN
:強引用關聯對象,原子操作,修飾詞爲 strong
、atomic
。
OBJC_ASSOCIATION_COPY
:複製關聯對象,原子操作,修飾詞爲 copy
、atomic
。
注意:OBJC_ASSOCIATION_ASSIGN
弱引用關聯對象,一般修飾詞爲 assign
、unsafe_unretained
和 weak
有區別,當對象銷燬時,指針的地址還是存在的,也就是說指針並沒有被置爲 nil
,再次訪問會造成野指針。
實現原理
objc_setAssociatedObject
下面我們看下 Runtime 的源碼。 以下源碼來自於[opensource.apple.com]
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { objc_setAssociatedObject_non_gc(object, key, value, policy); }
通過調用關係,Associated Objects
核心實現在 _object_set_associative_reference
方法裏面。
_object_set_associative_reference 函數
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { // retain the new value (if any) outside the lock. // 創建一個ObjcAssociation對象 ObjcAssociation old_association(0, nil); // 通過policy爲value創建對應屬性,如果policy不存在,則默認爲assign id new_value = value ? acquireValue(value, policy) : nil; { // 創建AssociationsManager對象 AssociationsManager manager; // 在manager取_map成員,其實是一個map類型的映射 AssociationsHashMap &associations(manager.associations()); // 創建指針指向即將擁有成員的Class // 至此該類已經包含這個關聯對象 disguised_ptr_t disguised_object = DISGUISE(object); // 以下是記錄強引用類型成員的過程 if (new_value) { // break any existing association. // 在即將擁有成員的Class中查找是否已經存在改關聯屬性 AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { // secondary table exists // 當存在時候,訪問這個空間的map ObjectAssociationMap *refs = i->second; // 遍歷其成員對應的key ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { // 如果存在key,重新更改Key的指向到新關聯屬性 old_association = j->second; j->second = ObjcAssociation(policy, new_value); } else { // 否則以新的key創建一個關聯 (*refs)[key] = ObjcAssociation(policy, new_value); } } else { // create the new association (first time). // key不存在的時候,直接創建關聯 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. // 這種情況是policy不存在或者爲assign的時候 // 在即將擁有的Class中查找是否已經存在Class // 其實這裏的意思就是如果之前有這個關聯對象,並且是非assign形的,直接erase AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { // 如果有該類型成員檢查是否有key ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { // 如果有key,記錄舊對象,釋放 old_association = j->second; refs->erase(j); } } } } // release the old value (outside of the lock). // 如果存在舊對象,則將其釋放 if (old_association.hasValue()) ReleaseValue()(old_association); }
源碼中得出結論
-
Associated Objects
是一個AssociationsManager
的結構體,維護了一個spinlock_t
鎖和一個_map
的哈希表。 -
_map
哈希表中的鍵爲disguised_ptr_t
。
inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); } #ifndef _UINTPTR_T #define _UINTPTR_T typedef unsigned long uintptr_t; #endif /* _UINTPTR_T */
其實 DISGUISE 函數其實僅僅對 object 做了下位運算,得到一個指向 self 地址的指針,通過這個指針,可以找到對應的 value,即一個 AssociationsHashMap 哈希表。
ObjectAssociationMap
#if TARGET_OS_WIN32 typedef hash_map<void *, ObjcAssociation> ObjectAssociationMap; typedef hash_map<disguised_ptr_t, ObjectAssociationMap *> AssociationsHashMap; #else typedef ObjcAllocator<std::pair<void * const, ObjcAssociation> > ObjectAssociationMapAllocator; class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> { public: void *operator new(size_t n) { return ::malloc(n); } void operator delete(void *ptr) { ::free(ptr); } }; typedef ObjcAllocator<std::pair<const disguised_ptr_t, ObjectAssociationMap*> > AssociationsHashMapAllocator; class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> { public: void *operator new(size_t n) { return ::malloc(n); } void operator delete(void *ptr) { ::free(ptr); } }; #endif
在 AssociationsHashMap
中 key
是 disguised_ptr_t
,Value
則是ObjectAssociationMap
。
在 ObjectAssociationMap
中以 key
是 self
指針,Value
則是 ObjcAssociation
。
ObjcAssociation
class ObjcAssociation { uintptr_t _policy; id _value; public: ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {} ObjcAssociation() : _policy(0), _value(nil) {} uintptr_t policy() const { return _policy; } id value() const { return _value; } bool hasValue() { return _value != nil; } };
ObjcAssociation
存儲着 _policy
、_value
,而這兩個值是調用 objc_setAssociatedObject
函數傳入的值。
總結:AssociationsHashMap
以 key-value
的形式保存從對象的 disguised_ptr_t
到 ObjectAssociationMap
的映射,而 ObjectAssociationMap
則保存了從 key
到關聯對象 ObjcAssociation
的映射,這個數據結構保存了當前對象對應的所有關聯對象,最後的 ObjcAssociation
存儲了 policy
以及 value
。
new_value
static id acquireValue(id value, uintptr_t policy) { switch (policy & 0xFF) { case OBJC_ASSOCIATION_SETTER_RETAIN: return ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain); case OBJC_ASSOCIATION_SETTER_COPY: return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy); } return value; }
根據 acquireValue
函數,把傳入的 value
通過對策略的判斷返回新的 new_value
。
當 new_value != nil
設置/更新關聯對象的值
// break any existing association. // 在即將擁有成員的Class中查找是否已經存在改關聯屬性 AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { // secondary table exists // 當存在時候,訪問這個空間的map ObjectAssociationMap *refs = i->second; // 遍歷其成員對應的key ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { // 如果存在key,重新更改Key的指向到新關聯屬性 old_association = j->second; j->second = ObjcAssociation(policy, new_value); } else { // 否則以新的key創建一個關聯 (*refs)[key] = ObjcAssociation(policy, new_value); } } else { // create the new association (first time). // key不存在的時候,直接創建關聯 ObjectAssociationMap *refs = new ObjectAssociationMap; associations[disguised_object] = refs; (*refs)[key] = ObjcAssociation(policy, new_value); object->setHasAssociatedObjects(); }
- 獲取唯一的保存關聯對象的哈希表
AssociationsHashMap
。 - 使用
DISGUISE(object)
作爲key
尋找對應的ObjectAssociationMap
。 - 如果沒有找到,初始化一個
ObjectAssociationMap
,再實例化ObjcAssociation
對象添加到Map
中,並調用setHasAssociatedObjects
方法,表明當前對象已經含有關聯對象。 - 如果找到了對應的
ObjectAssociationMap
,遍歷其成員對應的key
是否存在,如果存在key
,重新更改Key
的指向到新關聯屬性,否則以新的key
創建一個關聯。
如果 new_value == nil
,就要刪除對應 key
的關聯對象。
// setting the association to nil breaks the association. // 這種情況是policy不存在或者爲assign的時候 // 在即將擁有的Class中查找是否已經存在Class // 其實這裏的意思就是如果之前有這個關聯對象,並且是非assign形的,直接erase AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { // 如果有該類型成員檢查是否有key ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { // 如果有key,記錄舊對象,釋放 old_association = j->second; refs->erase(j); } }
當 policy
不存在或者爲 assign
的時候,
- 根據
DISGUISE(object)
作爲key
尋找對應的ObjectAssociationMap
。 - 如果找到了對應的
ObjectAssociationMap
,遍歷其成員對應的key
是否存在,如果存在key
,調用erase
方法,移除關聯關係。
// release the old value (outside of the lock). if (old_association.hasValue()) ReleaseValue()(old_association);
如果存在舊對象,則將其釋放。
藉助一張圖objc_setAssociatedObject 原理
image
objc_getAssociatedObject
objc_getAssociatedObject
內部調用的是 _object_get_associative_reference
id objc_getAssociatedObject(id object, const void *key) { return objc_getAssociatedObject_non_gc(object, key); }
_object_get_associative_reference
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; }
- 獲取唯一的保存關聯對象的哈希表
AssociationsHashMap
。 - 使用
DISGUISE(object)
作爲key
尋找對應的ObjectAssociationMap
。 - 如果找到了對應的
ObjectAssociationMap
,遍歷其成員對應的key
是否存在,如果存在key
,獲取ObjcAssociation
。 - 返回關聯對象
ObjcAssociation
的值。
objc_removeAssociatedObjects
objc_removeAssociatedObjects
用來刪除所有的關聯對象,objc_removeAssociatedObjects
函數內部調用的是 _object_remove_assocations
函數。
_object_remove_assocations
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()); }
- 獲取唯一的保存關聯對象的哈希表
AssociationsHashMap
。 - 如果哈希表
AssociationsHashMap
的size
爲0
,直接return
。 - 使用
DISGUISE(object)
作爲key
尋找對應的ObjectAssociationMap
。 - 如果找到了對應的
ObjectAssociationMap
,遍歷其成員對應的key
是否存在,如果存在key
,然後將所有的關聯結構保存到vector
中。 - 刪除關聯關係,釋放關聯對象。
參考鏈接
https://www.jianshu.com/p/79479a09a8c0 http://blog.leichunfeng.com/blog/2015/06/26/objective-c-associated-objects-implementation-principle/