在分類的源碼中,我們并沒有看到分類有能夠添加成員變量能力,但是我們可以通過關聯對象的方式來達到在分類中為對象添加成員變量的效果。 我們先來看看有關關聯對象的幾個方法:
// 根據key 從object對象中獲取對應的關聯值 并作為返回值返回
id objc_getAssociatedObject(id object, const void *key)
// 首先設置value值 然后object通過指定的key 通過指定的策略policy和value建立映射關系
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
// 移除object的所有關聯對象
void objc_removeAssociatedObjects(id object)
結合源碼,我們是可以知道分類中是沒有存儲成員變量的能力的。那么,我們在分類中添加的成員變量都被添加到哪里去了?
我們在分類中添加成員變量是使用關聯對象
的技術。而關聯對象可以達到添加成員變量的效果(注意,是效果,但是成員變量并沒有被實際添加到類中的!)。
關聯對象
我們都知道,類的定義是在編譯期間就已經在內存中固定了的,也是因為這個原因,我們是無法通過任何方法修改到內存中的固定區域的。因此,為了讓我們能夠在類中為對象添加
成員變量。蘋果讓我們能夠使用關聯對象的方式。為類添加需要的成員變量,其本質很簡單,就是在程序運行的過程中,創建了一個對象(AssociationsManager),這個對象負責維護一個全局容器Map(AssociationsHashMap),而這個Map就通過對象指針(DISGUISE(obj))又維護了子Map(ObjectAssociationMap),這個子Map通過我們傳入的key值,存儲著我們為類添加
的成員變量及其關聯策略。如下圖:
Xnip2018-10-18_23-03-52.png
我們再來通過源碼,分析一下關聯對象的具體實現。
這里我們主要分析void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
的實現:
// 我們可以看到 objc_setAssociatedObject 實際上就是透傳了_object_set_associative_reference方法,我們直接看_object_set_associative_reference方法的實現
/*
object: 要關聯的對象
key: 關聯的key
value: 關聯的z值
policy: 關聯策略
*/
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
/* 按照傳入的策略policy,對值進行包裝
static id acquireValue(id value, uintptr_t policy) {
switch (policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN: // retain 包裝
return ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
case OBJC_ASSOCIATION_SETTER_COPY: // copy 包裝
return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
}
return value;
}
*/
id new_value = value ? acquireValue(value, policy) : nil;
{
// 聲明了一個 AssociationsManager 這是一個全局對象
AssociationsManager manager;
// 被上面的 AssociationsManager 管理的全局Hashmap容器 可以理解為一個字典
AssociationsHashMap &associations(manager.associations());
// 根據對象的內存地址 計算出一個指針地址值 來作為某對象的唯一標識
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) { // 準備被關聯的對象有值
// break any existing association.
// 查找對象在全局容器中的map 如果這個對象不是第一次關聯對象,那這個容器在首次關聯對象就被創建好了
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) { // 找到了對象對應Map
// secondary table exists
// 取到對象關聯的ObjectAssociationMap
ObjectAssociationMap *refs = i->second;
// 通過key找到對應的 ObjcAssociation(這個結構封裝了關聯策略和值)
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) { // 找到了key相同的,則替換值
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else { // 沒找到則按key 關聯 ObjcAssociation
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else { // 沒找到對象對應Map
// create the new association (first time).
// 創建一個 ObjectAssociationMap 對象
ObjectAssociationMap *refs = new ObjectAssociationMap;
// 在全局容器中,用對象的key(disguised_object)關聯這個新創建的ObjectAssociationMap
associations[disguised_object] = refs;
// 通過傳入的key 關聯 ObjcAssociation(這個結構封裝了關聯策略和值)
(*refs)[key] = ObjcAssociation(policy, new_value);
// 設置對象已持有關聯對象的標識位
object->setHasAssociatedObjects();
}
} else { // 如果沒有值,系統就把這次操作當成是按照Key移除對象某個值的操作
// setting the association to nil breaks the association.
// 查找對象在全局容器中的map
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) { // 找到了map, 如果沒找到就什么也不做
// 找到對象對應的ObjectAssociationMap
ObjectAssociationMap *refs = i->second;
// 找到key在ObjectAssociationMap對應的值
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) { // 如果值存在, 如果不存在就什么都不做
// 找到 key 關聯的 ObjcAssociation
old_association = j->second;
// 擦除關聯
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
if (old_association.hasValue()) ReleaseValue()(old_association);
}
再看看關聯對象的本質
{
"0x4827298742": { // 被關聯的對象(地址)
@selector(text): { // 我們傳入的key
"value": "Hello", // 被關聯的值
"policy": "retain" // 關聯策略
},
@selector(title): {
"value": "a object",
"policy": "copy"
}
},
"0x3413513412": {
@selector(backgroundColor): {
"value": "0xff8205",
"policy": "retain"
}
}
}
到這里,關聯對象的實現大家基本都了解了吧~