關聯對象的實現原理【OC】

前言

AssociationedObject多用於在Category中爲特定類擴展成員變量,也有用於在運行時爲某些對象動態建立成員變量。AssociationedObject能夠說是一種特殊的成員變量。 這篇文章是來詳細解釋AssociationedObject的實現原理,篇幅較長。算法

相關方法

objc_AssociationPolicy

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_setAssociatedObject

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_getAssociatedObject

OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
複製代碼

關聯對象值的獲取方法。

經過關聯Key,在目標對象(object)中,獲取到關聯對象的值(返回值)。

objc_removeAssociatedObjects

/** * 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版的代碼。

objc_setAssociatedObject

在這個方法中有不少的數據結構,首先介紹下數據結構,幫助理解,也能夠先往下翻看主要流程,有看不明白的再回來查找。

AssociationsManager

// 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的全局變量引用。

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>

解釋下這個泛型的意思

  • _Key(key_type)對應disguised_ptr_t,disguised_ptr_t其實是unsigned long類型,這是用來存放指針的。
  • _Tp(mapped_type)對應ObjectAssociationMap *,這個代表了Map中存儲的值類型,後面會介紹ObjectAssociationMap。
  • _Hash(hasher)對應DisguisedPointerHash,提供hash算法。
  • _Pred(key_equal)對應的是DisguisedPointerEqual,提供equal算法(兩個指針相同則相等)。
  • _Alloc(allocator_type)對應的是構造函數的類型,這裏構造函數類型是ObjcAllocator<std::pair<const disguised_ptr_t, ObjectAssociationMap *> >,也是一個泛型,用std::pair實現Key是disguised_ptr_t,Value是ObjectAssociationMap *的鍵值對。

它實現了建立和刪除的功能。

disguised_ptr_t

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)指針。

ObjectAssociationMap

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的鍵值對。

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

acquireValue

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方法生成新的拷貝,其餘的策略不做處理。

setHasAssociatedObjects

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的概念,Tagged Pointer是一種爲了節約內存佔用的策略,它的原理是將指針對象的數據直接存於指針中,在64位系統中被引入。

好比,在64位系統中,一個指針爲8字節,當指針關聯的值小於8位的時候,系統會將指針轉化成Tagged Pointer,並在最後一個bit位加入TaggedPoint標識,這個指針的結構就變成了「0x存儲數據+TaggedPoint標識」的結構。

0xb000000000000032
  |---------------|-|
0x|----存儲數據----|標識|
複製代碼

這麼作的好處是,減小了內存的佔用,又由於數據不須要放入堆中,因此不須要malloc和free,因此讀取和運行速度都獲得了提高。

這個時候指針已經變成了一個值,而再也不是一個地址,因此它也沒有isa指針,因此要先作判斷。

ReleaseValue 和 releaseValue

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(關聯對象)的關係。

2019-03-14-AssociationedObject-1

objc_getAssociatedObject

下面分析的是關於關聯對象的取值邏輯,大部分結構體部分在設置值部分都說了,又遇到不理解的回頭去看,這裏直接將主要實現了。

主要實現

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;
}
複製代碼

objc_retain

id 
objc_retain(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}
複製代碼

在retian的時候也判斷了是否爲Tagged Pointer。

這裏會調用一次obj的retian方法,引用計數會加一。

objec_autorelease

id
objc_autorelease(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->autorelease();
}
複製代碼

與retian幾乎相同,調用的是obj的autorelease方法。

objc_removeAssociatedObjects

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的時候,並不會清理掉關聯對象,因此關聯對象是須要咱們手動維護的,用完及時清理,避免出現內存泄露。

總結

  • 能夠看出Runtime對於關聯對象的管理都是線程安全的,增刪查改都是加鎖的。
  • 在App運行期間,由AssociationsHashMap來管理全部被添加到對象中的關聯對象。
  • NSObject在dealloc的時候不會清理關聯對象,須要手動維護關聯對象的內存管理。
相關文章
相關標籤/搜索