接上一篇博客繼續
第二種方法
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添加成員變量也就能解釋通了