六、關聯對象分析
實則是為了解決分類創建屬性的問題
1.分類直接添加屬性的后果
- 編譯會出現警告:沒有setter方法和getter方法
-
運行會報錯:-[FXPerson setName:]: unrecognized selector sent to instance 0x100f61180'
2.為什么不能直接添加屬性
Category
在runtime
中是用一個結構體表示的:
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
...
};
里面雖然可以添加屬性變量,但是這些properties
并不會自動生成Ivar
,也就是不會有 @synthesize
的作用,dyld加載期間,這些分類會被加載并patch到相應的類中。這是一個動態過程,Ivar
不能動態添加
3.解決方案
手動實現setter、getter方法,關聯對象
- (void)setName:(NSString *)name {
/**
參數一:id object : 給哪個對象添加屬性,這里要給自己添加屬性,用self。
參數二:void * == id key : 屬性名,根據key獲取關聯對象的屬性的值,在objc_getAssociatedObject中通過次key獲得屬性的值并返回。
參數三:id value : 關聯的值,也就是set方法傳入的值給屬性去保存。
參數四:objc_AssociationPolicy policy : 策略,屬性以什么形式保存。
*/
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)name {
/**
參數一:id object : 獲取哪個對象里面的關聯的屬性。
參數二:void * == id key : 什么屬性,與objc_setAssociatedObject中的key相對應,即通過key值取出value。
*/
return objc_getAssociatedObject(self, @"name");
}
4.關聯對象原理
- setter方法——
objc_setAssociatedObject
分析
蘋果設計接口時往往會加個中間層——即使底層實現邏輯發生變化也不會影響到對外接口
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
跟進去看看_object_set_associative_reference
實現
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
// rdar://problem/44094390
if (!object && !value) return;
assert(object);
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));
// retain the new value (if any) outside the lock.
// 在鎖之外保留新值(如果有)。
ObjcAssociation old_association(0, nil);
// acquireValue會對retain和copy進行操作,
id new_value = value ? acquireValue(value, policy) : nil;
{
// 關聯對象的管理類
AssociationsManager manager;
// 獲取關聯的 HashMap -> 存儲當前關聯對象
AssociationsHashMap &associations(manager.associations());
// 對當前的對象的地址做按位去反操作 - 就是 HashMap 的key (哈希函數)
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// break any existing association.
// 獲取 AssociationsHashMap 的迭代器 - (對象的) 進行遍歷
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
ObjectAssociationMap *refs = i->second;
// 根據key去獲取關聯屬性的迭代器
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
// 替換設置新值
j->second = ObjcAssociation(policy, new_value);
} else {
// 到最后了 - 直接設置新值
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
// 如果AssociationsHashMap從沒有對象的關聯信息表,
// 那么就創建一個map并通過傳入的key把value存進去
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
} else {
// setting the association to nil breaks the association.
// 如果傳入的value是nil,并且之前使用相同的key存儲過關聯對象,
// 那么就把這個關聯的value移除(這也是為什么傳入nil對象能夠把對象的關聯value移除)
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
// 最后把之前使用傳入的這個key存儲的關聯的value釋放(OBJC_ASSOCIATION_SETTER_RETAIN策略存儲的)
if (old_association.hasValue()) ReleaseValue()(old_association);
}
-
ObjcAssociation old_association(0, nil)
處理傳進來的值得到new_value
- 獲取到管理所有關聯對象的hashmap總表的管理者
AssociationsManager
,然后拿到hashmap總表AssociationsHashMap
-
DISGUISE(object)
對關聯對象的地址進行取反操作得到哈希表對應的下標index
- 如果
new_value
為空(即對屬性賦值為nil)就直接找到相應的表進行刪除 - 如果
new_value
不為空,就拿到總表的迭代器通過拿到的下標index進行遍歷查找;如果找到管理對象的關聯屬性哈希map表,然后再通過key去遍歷取值- 如果取到了,就先把新值設置到key上,再將舊值釋放掉
- 如果沒取到,就直接將新值設置在key上
還是不明白就LLDB斷點調試唄
[圖片上傳中...(image-339b2c-1587710318659-18)]
<figcaption></figcaption>
- getter方法——
objc_getAssociatedObject
分析
id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
// 關聯對象的管理類
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
// 生成偽裝地址。處理參數 object 地址
disguised_ptr_t disguised_object = DISGUISE(object);
// 所有對象的額迭代器
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
// 內部對象的迭代器
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
// 找到 - 把值和策略讀取出來
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
// OBJC_ASSOCIATION_GETTER_RETAIN - 就會持有一下
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
objc_autorelease(value);
}
return value;
}
objc_getAssociatedObject
是objc_setAssociatedObject
的逆過程