关联对象实现原理

关联对象

使用runtime为Category动态关联对象

使用 runtime 给系统的类添加属性,首先需要了解对象与属性的关系。我们通过之前的学习知道,对象一开始初始化的时候其属性为nil,给属性赋值其实就是让属性指向一块存储内容的内存,使这个对象的属性跟这块内存产生一种关联。那么如果想动态的添加属性,其实就是动态的产生某种关联就好了。而想要给系统的类添加属性,只能通过分类。
这里给NSObject添加name属性,创建NSObject的分类,我们可以使用@property给分类添加属性。

1
@property(nonatomic,strong)NSString *name;
1
2
3
4
5
6
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, @"name");
}

动态添加属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
@param object 给哪个对象添加属性,这里要给自己添加属性,用self。
@param name 属性名,根据key获取关联对象的属性的值,在objc_getAssociatedObject中通过次key获得属性的值并返回。
@param value 关联的值,也就是set方法传入的值给属性去保存。
@param policy 策略,属性以什么形式保存。
*/
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, // 指定一个弱引用相关联的对象
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相关对象的强引用,非原子性
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // 指定相关的对象被复制,非原子性
OBJC_ASSOCIATION_RETAIN = 01401, // 指定相关对象的强引用,原子性
OBJC_ASSOCIATION_COPY = 01403 // 指定相关的对象被复制,原子性
};

key值只要是一个指针即可,我们可以传入@selector(name)

获得属性

1
2
3
4
5
/**
@param object 获取哪个对象里面的关联的属性。
@param key 属性键值,与objc_setAssociatedObject中的key相对应,即通过key值取出value。
*/
objc_getAssociatedObject(id object, const void *key);

移除所有关联对象

1
2
3
4
- (void)removeAssociatedObjects {
// 移除所有关联对象
objc_removeAssociatedObjects(self);
}

此时已经成功给NSObject添加name属性,并且NSObject对象可以通过点语法为属性赋值。

关联对象实现原理

实现关联对象技术的核心对象有

  1. AssociationsManager
  2. AssociationsHashMap
  3. ObjectAssociationMap
  4. ObjcAssociation

其中Map同我们平时使用的字典类似。通过key-value一一对应存值。对关联对象技术的核心对象有了一个大概的意识,我们通过源码来探寻这些对象的存在形式以及其作用。

objc_setAssociatedObject

首先找到 objc_setAssociatedObject 函数

1
2
3
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 函数,我们在看 _object_set_associative_reference 函数

_object_set_associative_reference

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
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);
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// break any existing association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
ObjectAssociationMap *refs = i->second;
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).
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.
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).
if (old_association.hasValue()) ReleaseValue()(old_association);
}

_object_set_associative_reference 函数内部我们可以全部找到我们上面说过的实现关联对象技术的核心对象,接下来我们来一个一个看其内部实现原理探寻他们之间的关系。

AssociationsManager

通过AssociationsManager内部源码发现,AssociationsManager内部有一个AssociationsHashMap对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
class AssociationsManager {
// associative references: object pointer -> PtrPtrHashMap.
static AssociationsHashMap *_map;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }

AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};

AssociationsHashMap

来看一下 AssociationsHashMap 内部的源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#if TARGET_OS_WIN32
typedef hash_map<void *, ObjcAssociation> ObjectAssociationMap;
typedef hash_map<disguised_ptr_t, ObjectAssociationMap *> AssociationsHashMap;
#else
typedef ObjcAllocator<std::pair<void * const, ObjcAssociation> > ObjectAssociationMapAllocator;
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
typedef ObjcAllocator<std::pair<const disguised_ptr_t, ObjectAssociationMap*> > AssociationsHashMapAllocator;
class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
#endif

通过 AssociationsHashMap 内部源码我们发现 AssociationsHashMap 继承自 unordered_map 首先来看一下 unordered_map 的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template <class _Key, class _Tp, class _Hash = hash<_Key>, class _Pred = equal_to<_Key>,
class _Alloc = allocator<pair<const _Key, _Tp> > >
class _LIBCPP_TEMPLATE_VIS unordered_map
{
public:
// types
typedef _Key key_type;
typedef _Tp mapped_type;
typedef _Hash hasher;
typedef _Pred key_equal;
typedef _Alloc allocator_type;
typedef pair<const key_type, mapped_type> value_type;
typedef pair<key_type, mapped_type> __nc_value_type;
typedef value_type& reference;
typedef const value_type& const_reference;
static_assert((is_same<value_type, typename allocator_type::value_type>::value),
"Invalid allocator::value_type");
...
}

unordered_map 源码中我们可以看出 _Key_Tp 也就是前两个参数对应着 map 中的 KeyValue,那么对照上面AssociationsHashMap 内源码发现 _Key 中传入的是 disguised_ptr_t_Tp 中传入的值则为 ObjectAssociationMap

紧接着我们来到 ObjectAssociationMap 中,上述代码中 ObjectAssociationMap 已经标记出,我们发现 ObjectAssociationMap 中同样以 keyValue 的方式存储着 ObjcAssociation

下面来看 ObjcAssociation

1
2
3
4
5
6
7
8
9
10
11
12
class ObjcAssociation {
uintptr_t _policy;
id _value;
public:
ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
ObjcAssociation() : _policy(0), _value(nil) {}

uintptr_t policy() const { return _policy; }
id value() const { return _value; }

bool hasValue() { return _value != nil; }
};

我们发现 ObjcAssociation 存储着 _policy_value ,而这两个值我们可以发现正是我们调用 objc_setAssociatedObject 函数传入的值,也就是说我们在调用 objc_setAssociatedObject 函数中传入的 valuepolicy 这两个值最终是存储在ObjcAssociation 中的。
现在我们已经对 AssociationsManagerAssociationsHashMapObjectAssociationMapObjcAssociation 四个对象之间的关系有了简单的认识,那么接下来我们来细读源码,看一下 objc_setAssociatedObject 函数中传入的四个参数分别放在哪个对象中充当什么作用。

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
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);
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// break any existing association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
ObjectAssociationMap *refs = i->second;
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).
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.
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).
if (old_association.hasValue()) ReleaseValue()(old_association);
}

细读上述源码我们可以发现,首先根据我们传入的 value 经过 acquireValue 函数处理获取 new_value。acquireValue函数内部其实是通过对策略的判断返回不同的值。

1
2
3
4
5
6
7
8
9
static id acquireValue(id value, uintptr_t policy) {
switch (policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
return objc_retain(value);
case OBJC_ASSOCIATION_SETTER_COPY:
return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
}
return value;
}

创建 AssociationsManager manager,以及拿到 manager 内部的 AssociationsHashMapassociations
之后我们看到了我们传入的第一个参数 objectobject 经过 DISGUISE 函数被转化为了 disguised_ptr_t 类型的 disguised_object

1
2
3
typedef uintptr_t disguised_ptr_t;
inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); }
inline id UNDISGUISE(disguised_ptr_t dptr) { return id(~dptr); }

DISGUISE 函数其实仅仅对object做了位运算

之后我们看到被处理成 new_valuevalue,同 policy 被存入了 ObjcAssociation 中。
ObjcAssociation 对应我们传入的 key 被存入了 ObjectAssociationMap 中。disguised_objectObjectAssociationMap 则以 key-value 的形式对应存储在 associations 中也就是 AssociationsHashMap 中。

1
2
3
4
5
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();

如果我们 value 设置为 nil 的话那么会执行下面的代码

1
2
3
4
5
6
7
8
9
10
// setting the association to nil breaks the association.
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);
}
}

从上述代码中可以看出,如果我们设置 valuenil 时,就会将关联对象从 ObjectAssociationMap 中移除。

通过上图得出,一个实例对象就对应一个 ObjectAssociationMap,而 ObjectAssociationMap 中存储着多个此实例对象的关联对象的 key 以及 ObjcAssociation,为 ObjcAssociation 中存储着关联对象的 valuepolicy 策略。

由此我们可以知道关联对象并不是放在了原来的对象里面,而是自己维护了一个全局的 map 用来存放每一个对象及其对应关联属性表格。

objc_getAssociatedObject

objc_getAssociatedObject 内部调用的是 _object_get_associative_reference

1
2
3
id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}

_object_get_associative_reference

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
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
AssociationsManager manager; // AssociationsManager
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) { // 哈希表中查找disguised_object
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key); // 如果存在取出ObjectAssociationMap,并在ObjectAssociationMap中查找key对应的value
if (j != refs->end()) {
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
objc_autorelease(value);
}
return value;
}

_object_get_associative_reference 函数内部可以看出,向 set 方法中那样,反向将 value 一层一层取出最后 return 出去。

objc_removeAssociatedObjects

objc_removeAssociatedObjects 用来删除所有的关联对象,objc_removeAssociatedObjects 函数内部调用的是_object_remove_assocations 函数

1
2
3
4
5
6
void objc_removeAssociatedObjects(id object)
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object);
}
}

_object_remove_assocations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (associations.size() == 0) return;
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs; // 删除
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
for_each(elements.begin(), elements.end(), ReleaseValue());
}

上述源码可以看出 _object_remove_assocations 函数将 object 对象向对应的所有关联对象全部删除。

总结

关联对象并不是存储在被关联对象本身内存中,而是存储在全局的统一的一个 AssociationsManager 中,如果设置关联对象为 nil,就相当于是移除关联对象。此时我们我们在回过头来看 objc_AssociationPolicy 属性的保存策略。

1
2
3
4
5
6
7
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, // 指定一个弱引用相关联的对象
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相关对象的强引用,非原子性
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // 指定相关的对象被复制,非原子性
OBJC_ASSOCIATION_RETAIN = 01401, // 指定相关对象的强引用,原子性
OBJC_ASSOCIATION_COPY = 01403 // 指定相关的对象被复制,原子性
};

我们会发现其中只有 RETAINCOPY 而为什么没有 weak 呢?
总过上面对源码的分析我们知道,object 经过 DISGUISE 函数被转化为了 disguised_ptr_t 类型的 disguised_object

1
disguised_ptr_t disguised_object = DISGUISE(object);

而同时我们知道, weak 修饰的属性,当没有拥有对象之后就会被销毁,并且指针置位 nil ,那么在对象销毁之后,虽然在 map 中既然存在值 object 对应的 AssociationsHashMap,但是因为 object 地址已经被置位 nil,会造成坏地址访问而无法根据 object 对象的地址转化为 disguised_object 了。

参考链接