关联对象的实现原理

提问

  • 关联对象被存储在什么地方,是不是存放在被关联对象本身的内存中?
  • 为什么 AssociationsHashMap 是使用 unordered_map,而 ObjectAssociationMap 使用 map
  • 使用关联对象有什么需要注意的地方
  • 为什么内存管理策略中没有 weak 选项,即 OBJC_ASSOCIATION_WEAK

阅读本文的前提

1
objc_setAssociatedObject(self, @selector(vn_cellCollectionView), proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

其中 self 我们称之为被关联对象,proxy 我们称之为 关联对象

关键数据结构

理解好以下几个结构之后,几乎不需要看源码就能推测 set/get 的原理

AssociationsManager

单例,全局唯一

AssociationsHashMap

AssociationsManager 的属性,全局唯一

std::unordered_map,存放对象与 ObjectAssociationMap(这里的对象指的是添加属性的分类)

ObjectAssociationMap

std::map,存放 key 与 ObjcAssociation

ObjcAssociation

存放 value 和 policy

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
void _object_set_associative_reference(id object, void *key, id value,
uintptr_t policy)
{
// 初始化一个旧值
ObjcAssociation old_association(0, nil);

// acquireValue 会对新的值进行 retain or copy,可以看下下面的源码
id new_value = value ? acquireValue(value, policy) : nil;

{
// 全局单例
AssociationsManager manager;
// 全局的 unordered_map
AssociationsHashMap &associations(manager.associations());
// 将被关联对象转化一下,DISGUISE 仅仅对 object 做了位运算
disguised_ptr_t disguised_object = DISGUISE(object);

if (new_value)
{
// 新值不为空,则开始插入
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end())
{
// 如果被关联对象已经在全局的 unordered_map 注册过,则找到对应的 map
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end())
{
// map 找到了则记录旧值以备释放,并更新新值
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
}
else
{
// map 找不到则直接插入
(*refs)[key] = ObjcAssociation(policy, new_value);
}
}
else
{
// 如果被关联对象没有在全局的 unordered_map 注册过,则创建 map 并注册
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
// 将值插入到新的 map 中
(*refs)[key] = ObjcAssociation(policy, new_value);
_class_setInstancesHaveAssociatedObjects(_object_getClass(object));
}
}
else
{
// 新的值为空,准备删除旧值(如果有的话)
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())
{
// 记录旧值等待释放并从 map 中移除
old_association = j->second;
refs->erase(j);
}
}
}
}

// 释放旧值(如果需要的话)
if (old_association.hasValue())
ReleaseValue()(old_association);
}
1
2
3
4
5
6
7
8
9
10
11
static id acquireValue(id value, uintptr_t policy) 
{
switch (policy & 0xFF)
{
case OBJC_ASSOCIATION_SETTER_RETAIN:
return ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
case OBJC_ASSOCIATION_SETTER_COPY:
return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
}
return value;
}
1
2
// DISGUISE 仅仅对 object 做了位运算
inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); }

解答

  • 关联对象被存储在什么地方,是不是存放在被关联对象本身的内存中?

    关联对象并不是存放在被关联对象本身的内存中,而是存放在一个全局的 unordered_map

    其中 unordered_map 存储着被关联的对象与 map,其中 map 存放着关联对象的 key 和关联对象

    获取关联对象时,首先通过被关联对象的地址找到 map,然后通过关联对象的 key 找到关联对象并返回

  • 为什么 AssociationsHashMap 是使用 unordered_map,而 ObjectAssociationMap 使用 map

    unordered_map 查找更加高效,大多数情况下应该选择 unordered_map,而 ObjectAssociationMap 使用 map 应该是出于内存考虑

    详细可参考 unordered_mapmap 的区别

更多阅读

  1. 提问
  2. 阅读本文的前提
  3. 关键数据结构
    1. AssociationsManager
    2. AssociationsHashMap
    3. ObjectAssociationMap
    4. ObjcAssociation
  4. 源码
  5. 解答
  6. 更多阅读