Runtime objc4-779.1 爲什麼不能向一個已存在的類添加成員變量?有什麼辦法達到相同的效果(2)?

上一篇博客繼續

第二種方法

Func2 利用關聯實現對已存在的類添加成員變量的效果

涉及兩個主要的API

給某一個對象關聯一個對象
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)

獲取某一個對象的關聯對象
id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)

舉個例子

objc_setAssociatedObject(self.father, "testKey", @"我是testKey的value", OBJC_ASSOCIATION_RETAIN);
NSLog(@"關聯對象testKey的value:%@",objc_getAssociatedObject(self.father, "testKey"));

打印結果:2020-03-06 10:54:25.145628+0800 XSTest[30259:1297570] 關聯對象testKey的value:我是testKey的value

用起來比較簡單,API的具體用法百度即可,我們主要看,關聯是如何實現的,我們一步步追溯objc_setAssociatedObject後邊的源碼

Step1 objc-runtime.mm line 652

static void
_base_objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
  _object_set_associative_reference(object, key, value, policy);
}
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    // 判空
    if (!object && !value) return;

	// 判斷本類對象是否允許關聯其他對象.如果允許則進入代碼塊
    if (object->getIsa()->forbidsAssociatedObjects())
        _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));

	// 將被關聯的對象封裝成DisguisedPtr方便在後邊hash表中的管理,它的作用就像是一個指針
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    // 將需要關聯的對象,封裝成ObjcAssociation,方便管理
    ObjcAssociation association{policy, value};

    // 處理policy爲retain和copy的修飾情況,
    association.acquireValue();

    {
    	// 獲取關聯對象管理者對象
        AssociationsManager manager;
        // 根據管理者對象獲取對應關聯表(HashMap)
        AssociationsHashMap &associations(manager.get());

        if (value) {
        	// 如果這個disguised存在於ObjectAssociationMap()中,則替換,如果不存在則初始化後在插入
        	// 這裏說明一下,我們關聯的對象關係存在於ObjectAssociationMap中,而
        	//	ObjectAssociationMap有多個,所以,這一步是對ObjectAssociationMap的一個管理,下邊纔是對我們要關聯的對象的操作
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            // 如果這是此對象第一次被關聯
            if (refs_result.second) {
               // 修改isa_t中的has_assoc字段,標記其被關聯狀態
                object->setHasAssociatedObjects();
            }

            // 這裏纔是對我們要關聯的對象操作
            auto &refs = refs_result.first->second;
            // 想map中插入key value對
            auto result = refs.try_emplace(key, std::move(association));
            // 這裏沒有看懂,爲什麼沒有第二個就要交換一下..
            if (!result.second) {
                association.swap(result.first->second);
            }
        } else {
        	// value爲空, 並且在associations中有記錄,則進行擦除操作 
            auto refs_it = associations.find(disguised);
            if (refs_it != associations.end()) {
                auto &refs = refs_it->second;
                auto it = refs.find(key);
                if (it != refs.end()) {
                    association.swap(it->second);
                    refs.erase(it);
                    if (refs.size() == 0) {
                        associations.erase(refs_it);
                    }
                }
            }
        }
    }

    // release the old value (outside of the lock).
    association.releaseHeldValue();
}

所以我們通過這個插入的代碼可以看出來存儲關聯對象的是hash表,代碼關係如下圖所示
在這裏插入圖片描述

圖是從http://www.cocoachina.com/cms/wap.php?action=article&id=23892轉過來的,感謝作者,侵權刪除

從數據結構和插入代碼我們看出,之所以關聯可以在運行時向已存在類中添加成員變量的原因是因爲,被添加的成員變量實際並沒有存儲在類的內存中,而是在這個樹狀結構中的某一張hash表中以key value pair的形式存儲, 獲取也根據key獲取, 所以, 它爲什麼可以給category添加成員變量也就能解釋通了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章