AssociationedObject多用於在Category中爲特定類擴展成員變量,也有用於在運行時爲某些對象動態建立成員變量。AssociationedObject能夠說是一種特殊的成員變量。 這篇文章是來詳細解釋AssociationedObject的實現原理,篇幅較長。算法
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};
複製代碼
這是在調用objc_setAssociatedObject
時須要用到的參數,用於指定關聯參數的引用策略。安全
OBJC_ASSOCIATION_ASSIGNbash
將關聯引用描述爲弱引用數據結構
OBJC_ASSOCIATION_RETAIN_NONATOMICapp
將關聯引用描述爲強引用,而且非原子性。less
OBJC_ASSOCIATION_COPY_NONATOMICide
將關聯引用描述爲拷貝引用,而且非原子性。函數
OBJC_ASSOCIATION_RETAINui
將關聯引用描述爲強引用,而且爲原子性。this
OBJC_ASSOCIATION_COPY
將關聯引用表述爲拷貝引用,而且爲原子性。
這一段比較好理解,與Property同樣,關聯引用也能夠設置引用描述。
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
複製代碼
建立和設置一個關聯對象的方法。
將一個選定值(value)經過Key-Value的形式掛載在目標對象(object)上,同時指定關聯的策略(policy),這樣就能生成一個關聯對象。
經過將目標對象(object)上指定的Key對應的值(value)設置nil,便可以將已存在的關聯對象清除。
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
複製代碼
關聯對象值的獲取方法。
經過關聯Key,在目標對象(object)中,獲取到關聯對象的值(返回值)。
/** * Removes all associations for a given object. * * @param object An object that maintains associated objects. * * @note The main purpose of this function is to make it easy to return an object * to a "pristine state」. You should not use this function for general removal of * associations from objects, since it also removes associations that other clients * may have added to the object. Typically you should use \c objc_setAssociatedObject * with a nil value to clear an association. * * @see objc_setAssociatedObject * @see objc_getAssociatedObject */
OBJC_EXPORT void objc_removeAssociatedObjects(id object)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
複製代碼
移除目標對象(objct)的全部關聯對象。
官方在這作了提示:這個方法的主要目的是爲了讓對象更容易返回到原始狀態,調用此方法會將綁定在這個目標對象上的全部關聯對象都清除。若是別的開發也爲這個object設置了關聯對象,或者你在別的模塊中也爲其設置了不一樣的關聯對象,調用此方法後會被一併刪除。一般清除關聯對象,請經過objc_setAssociatedObject將關聯對象設置爲nil的方式來清除關聯對象。
在底層實現上,分爲GC版和無GC版,GC是MacOS的垃圾回收機制,不過如今也被棄用了,推薦使用ARC。而iOS上只有MRC和ARC,所以這裏只截取了無GC版的代碼。
在這個方法中有不少的數據結構,首先介紹下數據結構,幫助理解,也能夠先往下翻看主要流程,有看不明白的再回來查找。
// class AssociationsManager manages a lock / hash table singleton pair.
// Allocating an instance acquires the lock, and calling its assocations()
// method lazily allocates the hash table.
spinlock_t AssociationsManagerLock;
class AssociationsManager {
// associative references: object pointer -> PtrPtrHashMap.
static AssociationsHashMap *_map;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
AssociationsHashMap *AssociationsManager::_map = NULL;
複製代碼
AssociationsManager的做用管理lock,當它被建立的時候會加鎖,在它被析構的時候會釋放鎖,同時,有一個全局變量_map,管理的是目標對象與HashMap(Key-Value都爲指針)的的關係。
AssociationsManager中有一個全局變量類型爲AssociationsHashMap的_map引用。
提供了一個建立AssociationsHashMap的左值引用方法,_map是引用,*_map則是解引用又變成了AssociationsHashMap對象,函數前加了&(取地址符),返回的是AssociationsHashMap對象引用地址。
同時聲明瞭一個類型爲AssociationsHashMap的全局變量引用。
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); }
};
// unordered_map的泛型定義
template <class _Key,
class _Tp,
class _Hash = hash<_Key>,
class _Pred = equal_to<_Key>,
class _Alloc = allocator<pair<const _Key, _Tp> > >
複製代碼
AssociationsHashMap
繼承自unordered_map <disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator>
解釋下這個泛型的意思
它實現了建立和刪除的功能。
typedef unsigned long uintptr_t;
typedef uintptr_t disguised_ptr_t;
inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); }
inline id UNDISGUISE(disguised_ptr_t dptr) { return id(~dptr); }
複製代碼
這裏能夠看出disguised_ptr_t
其實是一個unsigned long,它的長度與指針相同,因此被當作指針使用。 ~uintptr_t(value)
:value自己也是個對象指針,將它包裝成unsigned long類型,載逐位取反後返回。 id(~dptr)
:將disguised_ptr_t逐位取反後,返回對象(id)指針。
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); }
};
// std::map的泛型定義
template <class _Key,
class _Tp,
class _Compare = less<_Key>,
class _Allocator = allocator<pair<const _Key, _Tp> > >
複製代碼
與AssociationsHashMap相似,ObjectAssociationMap
繼承自public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator>
泛型的意思是:
_key(key_type)對應的是void *,即無類型指針,表示內存地址。
_Tp(mapped_type)對應[ObjcAssociation](#### ObjcAssociation),這個上面提到過,用來存放關聯對象的值與關聯策略。
_Compare(key_compare)對應的是ObjectPointerLess,提供比較算法(比對指針地址)。
_Allocator(allocator_type)對應的是ObjcAllocator<std::pair<void * const, ObjcAssociation> >,用std::pair實現Key是void * const(內存地址),Value是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; }
};
複製代碼
這是關聯對象的結構,存儲着關聯策略_policy
和關聯對象的值_value
。
static id acquireValue(id value, uintptr_t policy) {
switch (policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
return objc_retain(value);
case OBJC_ASSOCIATION_SETTER_COPY:
return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
}
return value;
}
複製代碼
判斷引用策略,若是爲Retian的,則會調用objc_retain
將value的引用計數加一,若是是Copy,則會調用Value的copy方法生成新的拷貝,其餘的策略不做處理。
inline void
objc_object::setHasAssociatedObjects()
{
if (isTaggedPointer()) return;
retry:
isa_t oldisa = LoadExclusive(&isa.bits);
isa_t newisa = oldisa;
if (!newisa.nonpointer || newisa.has_assoc) {
ClearExclusive(&isa.bits);
return;
}
newisa.has_assoc = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}
複製代碼
這裏主要作的是將isa
中的has_assoc
標誌位設置爲true。
這裏涉及到了Tagged Pointer的概念,Tagged Pointer是一種爲了節約內存佔用的策略,它的原理是將指針對象的數據直接存於指針中,在64位系統中被引入。
好比,在64位系統中,一個指針爲8字節,當指針關聯的值小於8位的時候,系統會將指針轉化成Tagged Pointer,並在最後一個bit位加入TaggedPoint標識,這個指針的結構就變成了「0x存儲數據+TaggedPoint標識」的結構。
0xb000000000000032
|---------------|-|
0x|----存儲數據----|標識|
複製代碼
這麼作的好處是,減小了內存的佔用,又由於數據不須要放入堆中,因此不須要malloc和free,因此讀取和運行速度都獲得了提高。
這個時候指針已經變成了一個值,而再也不是一個地址,因此它也沒有isa指針,因此要先作判斷。
struct ReleaseValue {
void operator() (ObjcAssociation &association) {
releaseValue(association.value(), association.policy());
}
};
static void releaseValue(id value, uintptr_t policy) {
if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) {
return objc_release(value);
}
}
複製代碼
這裏是關聯對象釋放的相關代碼,若是引用策略爲Retian的話,釋放時,會將引用計數減一。
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// 建立一個 ObjcAssociation對象,初始化policy爲OBJC_ASSOCIATION_ASSIGN,value爲nil。
ObjcAssociation old_association(0, nil);
// 根據引用策略處理value。若是Copy,調用value的copy方法獲取new_value,若是是Retain的,則持有value,value的引用計數加一,其餘策略不做處理。
id new_value = value ? acquireValue(value, policy) : nil;
{
// 建立一個AssociationsManager,在初始化時會加鎖,在析構時會解鎖。
// C++會默認爲對象進行初始化,即已經加鎖。
AssociationsManager manager;
// 獲取AssociationsManager中全局變量AssociationsHashMap的引用,
// 調用AssociationsHashMap的拷貝構造函數
// 用上面的引用直接初始化新的associations變量。
AssociationsHashMap &associations(manager.associations());
// 建立一個disguised_ptr_t,調用DISGUISE(object),使用目標對象初始化。
disguised_ptr_t disguised_object = DISGUISE(object);
// 若是new_value 不爲nil,情景是設置新的關聯值、或者修改關聯值。
if (new_value) {
// 在AssociationsHashMap中經過迭代器查找,
// 與disguised_ptr_t相對應的ObjectAssociationMap*(i)
// 注:map的結構爲std::pair<const disguised_ptr_t, ObjectAssociationMap*>。
AssociationsHashMap::iterator i = associations.find(disguised_object);
// 若是查找到Map中對應的ObjectAssociationMap*(i),即修改關聯值的狀況。
// 注:在C++的map中,若是未查找到元素迭代器會返回map.end();
if (i != associations.end()) {
// 將ObjectAssociationMap*(refs)
// 指向AssociationsHashMap(i)的second,
// 這是Map的Value。
ObjectAssociationMap *refs = i->second;
// 在ObjectAssociationMap*(refs)中經過key,
// 來查找對應存在的ObjectAssociationMap對象(j)。
ObjectAssociationMap::iterator j = refs->find(key);
// 若是查找到了對應的ObjcAssociation(j)。
// 注:map的結構爲std::pair<void * const, ObjcAssociation>
if (j != refs->end()) {
// 將ObjectAssociationMap的value,
// 即ObjcAssociation,賦值給old_association
old_association = j->second;
// 將這個Map的Value更新爲新構建的ObjcAssociation。
j->second = ObjcAssociation(policy, new_value);
} else {
// 若是j是容器惟一的元素,
// 爲ObjectAssociationMap的引用(*refs)設置一個Map,
// key爲傳入的key,Value爲新構建的ObjcAssociation。
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// 若是Object是容器裏惟一的元素
// 建立一個ObjectAssociationMap
ObjectAssociationMap *refs = new ObjectAssociationMap;
// 在AssociationsHashMap(associations)中,
// 以Key爲disguised_object存儲。
associations[disguised_object] = refs;
// 爲ObjectAssociationMap建立,Key爲傳入值key,
// value爲新構建的ObjcAssociation。
(*refs)[key] = ObjcAssociation(policy, new_value);
// 調用關聯目標對象的setHasAssociatedObjects方法。
// 將對象的isa中的has_assoc設置爲true
// 即標識這個object有關聯對象存在。
// 這個方法位於objc-objct內。
object->setHasAssociatedObjects();
}
} else {
// 當new_value = nil時,
// 經過disguised_object查找對應的ObjectAssociationMap(i)
AssociationsHashMap::iterator i = associations.find(disguised_object);
// 若是查到了對應的ObjectAssociationMap(i)
if (i != associations.end()) {
// 取出ObjectAssociationMap *refs
ObjectAssociationMap *refs = i->second;
// 經過傳入的key查找對應的ObjcAssociation(j)
ObjectAssociationMap::iterator j = refs->find(key);
// 若是查到了對應的ObjcAssociation(j)
if (j != refs->end()) {
// 將舊值取出,賦值給old_association。
old_association = j->second;
// 將ObjcAssociation(j)從ObjectAssociationMap中移除。
refs->erase(j);
}
}
}
//到這裏釋放鎖。
}
// 若是存在舊值,則將舊值釋放。
if (old_association.hasValue()) ReleaseValue()(old_association);
}
複製代碼
看到這裏能夠總結一下關聯關係的存儲結構了。
AssociationsHashMap是管理目標對象(object)與ObjectAssociationMap的關係
ObjectAssociationMap是管理Key與ObjectAssociation(關聯對象)的關係。
下面分析的是關於關聯對象的取值邏輯,大部分結構體部分在設置值部分都說了,又遇到不理解的回頭去看,這裏直接將主要實現了。
id _object_get_associative_reference(id object, void *key) {
// 建立一個value。
id value = nil;
// 默認引用策略爲assign。
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
// 建立AssociationsManager並加鎖。
AssociationsManager manager;
// 獲取全局AssociationsHashMap。
AssociationsHashMap &associations(manager.associations());
// 經過object構建disguised_object。
disguised_ptr_t disguised_object = DISGUISE(object);
// 根據disguised_object查找對應的ObjectAssociationMap。
AssociationsHashMap::iterator i = associations.find(disguised_object);
// 若是查到了ObjectAssociationMap。
if (i != associations.end()) {
// 提取ObjectAssociationMap。
ObjectAssociationMap *refs = i->second;
// 根據key查找對應的ObjcAssociation。
ObjectAssociationMap::iterator j = refs->find(key);
// 若是ObjcAssociation存在。
if (j != refs->end()) {
// 獲取ObjcAssociation
ObjcAssociation &entry = j->second;
// 將value設置爲ObjcAssociation的value。
value = entry.value();
// 將policy設置爲ObjcAssociation的policy。
policy = entry.policy();
// 若是引用策略是Retain模式
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
// 調用value的retian方法,引用計數加一。
objc_retain(value);
}
}
}
//釋放鎖
}
// 若是值存在,而且引用策略是AutoRelease模式。
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
// 調用value的autorelease方法,將value設置爲autorelease。
objc_autorelease(value);
}
// 返回取到的關聯對象
return value;
}
複製代碼
id
objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
複製代碼
在retian的時候也判斷了是否爲Tagged Pointer。
這裏會調用一次obj的retian方法,引用計數會加一。
id
objc_autorelease(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->autorelease();
}
複製代碼
與retian幾乎相同,調用的是obj的autorelease方法。
void _object_remove_assocations(id object) {
// 聲明一個vector容器,key類型是ObjcAssociation,value類型是ObjcAllocator<ObjcAssociation>,這是一個構造器結構體,在runtime時內部使用。
// vector在C++中是一個動態長度的順序容器。
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
//建立一個AssociationsManager並加鎖。
AssociationsManager manager;
// 獲取AssociationsHashMap。
AssociationsHashMap &associations(manager.associations());
// 若是AssociationsHashMap中沒有沒有元素,則結束流程。
if (associations.size() == 0) return;
// 經過object構造disguised_object。
disguised_ptr_t disguised_object = DISGUISE(object);
// 在AssociationsHashMap中查找disguised_object對應的ObjectAssociationMap。
AssociationsHashMap::iterator i = associations.find(disguised_object);
// 若是ObjectAssociationMap存在。
if (i != associations.end()) {
// 得到ObjectAssociationMap。
ObjectAssociationMap *refs = i->second;
// 循環迭代ObjectAssociationMap
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
//將全部的ObjectAssociation都存入vector中。
elements.push_back(j->second);
}
// 釋放ObjectAssociationMap的內存空間。
delete refs;
// 在AssociationsHashMap中刪除這個ObjectAssociationMap元素。
associations.erase(i);
}
}
// 循環釋放vector中保存的每個ObjectAssociation
for_each(elements.begin(), elements.end(), ReleaseValue());
}
複製代碼
//文件:runtime.h
/** * Destroys an instance of a class without freeing memory and removes any * associated references this instance might have had. * 摧毀一個實例對象,不會釋放和移除任何此實例對象的關聯對象。 * * @param obj The class instance to destroy. * * @return \e obj. Does nothing if \e obj is nil. * * @note CF and other clients do call this under GC. */
OBJC_EXPORT void * _Nullable objc_destructInstance(id _Nullable obj)
OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0, 2.0)
OBJC_ARC_UNAVAILABLE;
/*********************************************************************** * object_dispose * fixme * Locking: none 文件:objc-runtime-new.m **********************************************************************/
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
// ===============文件:objc-private.h ========================
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
// ===============文件:NSObject.mm ========================
void
_objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
// Replaced by NSZombies
- (void)dealloc {
_objc_rootDealloc(self);
}
複製代碼
從上面的代碼能夠看出,當NSObject執行dealloc的時候,並不會清理掉關聯對象,因此關聯對象是須要咱們手動維護的,用完及時清理,避免出現內存泄露。