簡介
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 原理
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
中。 -
刪除關聯關係,釋放關聯物件。
參考連結
www.jianshu.com/p/79479a09a… blog.leichunfeng.com/blog/2015/0…