简介

​Associated Objects​​ 是 ​​Objective-C 2.0​​ 中 ​​Runtime​​ 的特性之一。在 ​ ​<objc/runtime.h>​​ 中定义的三个方法,

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, const void *key);
void objc_removeAssociatedObjects(id object);
复制代码


从上面可以看出,​ ​objc_setAssociatedObject​​ 用于给对象添加关联对象,传入 ​​nil​​ 则可以移除已有的关联对象;​ ​objc_getAssociatedObject​​ 用于获取关联对象;​ ​objc_removeAssociatedObjects​​ 用于移除一个对象的所有关联对象。​ ​object​​:传入关联对象的所属对象,也就是增加成员的实例对象,一般来说传入 ​​self​​。​ ​key​​:唯一标记,即可以使用 ​​static char​​ 作为 ​​key​​ 值,也可以使用 ​​static void *kAssociatedObjectKey​​ 指针作为 ​​key​​ 值,当然推荐使用 用 ​​selector​​,使用 ​​getter​​ 方法的名称作为 ​​key​​ 值,可以利用 ​​_cmd​​ 来方便的取出 ​​selector​​。​ ​value​​:传入关联对象。​ ​policy​​:​​objc_AssociationPolicy​​ 是一个 ​​Objective-C​​ 枚举类型,也代表关联策略。

注意:objc_removeAssociatedObjects这个方法会移除一个对象的所有关联对象,一般通过给 ​​objc_setAssociatedObject​​ 函数传入 ​​nil​​ 来移除某个已有的关联对象。

关联策略

​OBJC_ASSOCIATION_ASSIGN​​:弱引用关联对象,一般修饰词为 ​​assign​​、​​unsafe_unretained​​。​ ​OBJC_ASSOCIATION_RETAIN_NONATOMIC​​:强引用关联对象,非原子操作,修饰词为 ​​strong​​、​​nonatomic​​。​ ​OBJC_ASSOCIATION_COPY_NONATOMIC​​:复制关联对象,非原子操作,修饰词为 ​​copy​​、​​nonatomic​​。​ ​OBJC_ASSOCIATION_RETAIN​​:强引用关联对象,原子操作,修饰词为 ​​strong​​、​​atomic​​。​ ​OBJC_ASSOCIATION_COPY​​:复制关联对象,原子操作,修饰词为 ​​copy​​、​​atomic​​。

注意:​​OBJC_ASSOCIATION_ASSIGN​​ 弱引用关联对象,一般修饰词为 ​​assign​​、​​unsafe_unretained​​ 和 ​​weak​​ 有区别,当对象销毁时,指针的地址还是存在的,也就是说指针并没有被置为 ​​nil​​,再次访问会造成野指针。

实现原理

objc_setAssociatedObject

下面我们看下 Runtime 的源码。以下源码来自于[​ opensource.apple.com]

void objc_setAssociatedObject(id object, const void *key, id value, 
objc_AssociationPolicy policy) {
objc_setAssociatedObject_non_gc(object, key, value, policy);
}
复制代码


通过调用关系,​​Associated Objects​​ 核心实现在 ​​_object_set_associative_reference​​ 方法里面。

_object_set_associative_reference 函数
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
// 创建一个ObjcAssociation对象
ObjcAssociation old_association(0, nil);
// 通过policy为value创建对应属性,如果policy不存在,则默认为assign
id new_value = value ? acquireValue(value, policy) : nil;
{
// 创建AssociationsManager对象
AssociationsManager manager;
// 在manager取_map成员,其实是一个map类型的映射
AssociationsHashMap &associations(manager.associations());
// 创建指针指向即将拥有成员的Class
// 至此该类已经包含这个关联对象
disguised_ptr_t disguised_object = DISGUISE(object);
// 以下是记录强引用类型成员的过程
if (new_value) {
// break any existing association.
// 在即将拥有成员的Class中查找是否已经存在改关联属性
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
// 当存在时候,访问这个空间的map
ObjectAssociationMap *refs = i->second;
// 遍历其成员对应的key
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
// 如果存在key,重新更改Key的指向到新关联属性
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {
// 否则以新的key创建一个关联
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
// key不存在的时候,直接创建关联
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.
// 这种情况是policy不存在或者为assign的时候
// 在即将拥有的Class中查找是否已经存在Class
// 其实这里的意思就是如果之前有这个关联对象,并且是非assign形的,直接erase
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// 如果有该类型成员检查是否有key
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
// 如果有key,记录旧对象,释放
old_association = j->second;
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
// 如果存在旧对象,则将其释放
if (old_association.hasValue()) ReleaseValue()(old_association);
}
复制代码


源码中得出结论

  • ​Associated Objects​​ 是一个 ​​AssociationsManager​​ 的结构体,维护了一个 ​​spinlock_t​​ 锁和一个 ​​_map​​ 的哈希表。
  • ​_map​​ 哈希表中的键为 ​​disguised_ptr_t​​。
inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); }
#ifndef _UINTPTR_T
#define _UINTPTR_T
typedef unsigned long uintptr_t;
#endif /* _UINTPTR_T */
复制代码


其实 DISGUISE 函数其实仅仅对 object 做了下位运算,得到一个指向 self 地址的指针,通过这个指针,可以找到对应的 value,即一个 AssociationsHashMap 哈希表。

ObjectAssociationMap
#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​​ 中 ​​key​​ 是 ​​disguised_ptr_t​​,​​Value​​ 则是​​ObjectAssociationMap​​。在 ​ ​ObjectAssociationMap​​ 中以 ​​key​​ 是 ​​self​​ 指针,​​Value​​ 则是 ​​ObjcAssociation​​。

ObjcAssociation
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​​ 函数传入的值。

总结:​​AssociationsHashMap​​ 以 ​​key-value​​ 的形式保存从对象的 ​​disguised_ptr_t​​ 到 ​​ObjectAssociationMap​​ 的映射,而 ​​ObjectAssociationMap​​ 则保存了从 ​​key​​ 到关联对象 ​​ObjcAssociation​​ 的映射,这个数据结构保存了当前对象对应的所有关联对象,最后的 ​​ObjcAssociation​​ 存储了 ​​policy​​ 以及 ​​value​​。

new_value
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;
}
复制代码


根据 ​​acquireValue​​ 函数,把传入的 ​​value​​ 通过对策略的判断返回新的 ​​new_value​​。

当 ​​new_value != nil​​ 设置/更新关联对象的值

// break any existing association.
// 在即将拥有成员的Class中查找是否已经存在改关联属性
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
// 当存在时候,访问这个空间的map
ObjectAssociationMap *refs = i->second;
// 遍历其成员对应的key
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
// 如果存在key,重新更改Key的指向到新关联属性
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {
// 否则以新的key创建一个关联
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
// key不存在的时候,直接创建关联
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
复制代码


  • 获取唯一的保存关联对象的哈希表 AssociationsHashMap
  • 使用 DISGUISE(object) 作为 key 寻找对应的 ObjectAssociationMap
  • 如果没有找到,初始化一个 ObjectAssociationMap,再实例化 ObjcAssociation 对象添加到 Map 中,并调用 setHasAssociatedObjects 方法,表明当前对象已经含有关联对象。
  • 如果找到了对应的 ObjectAssociationMap,遍历其成员对应的 key 是否存在,如果存在 key,重新更改 Key 的指向到新关联属性,否则以新的 key 创建一个关联。

如果 ​​new_value == nil​​,就要删除对应 ​​key​​ 的关联对象。

// setting the association to nil breaks the association.
// 这种情况是policy不存在或者为assign的时候
// 在即将拥有的Class中查找是否已经存在Class
// 其实这里的意思就是如果之前有这个关联对象,并且是非assign形的,直接erase
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// 如果有该类型成员检查是否有key
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
// 如果有key,记录旧对象,释放
old_association = j->second;
refs->erase(j);
}
}
复制代码


当 ​​policy​​ 不存在或者为 ​​assign​​ 的时候,

  • 根据 ​​DISGUISE(object)​​ 作为 ​​key​​ 寻找对应的 ​​ObjectAssociationMap​​。
  • 如果找到了对应的 ​​ObjectAssociationMap​​,遍历其成员对应的 ​​key​​ 是否存在,如果存在 ​​key​​,调用 ​​erase​​ 方法,移除关联关系。
// release the old value (outside of the lock).
if (old_association.hasValue()) ReleaseValue()(old_association);
复制代码


如果存在旧对象,则将其释放。

借助一张图objc_setAssociatedObject 原理


objc_getAssociatedObject

​objc_getAssociatedObject​​ 内部调用的是 ​​_object_get_associative_reference​

id objc_getAssociatedObject(id object, const void *key) {
return objc_getAssociatedObject_non_gc(object, key);
}
复制代码


_object_get_associative_reference
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
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();
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
}
return value;
}
复制代码


  • 获取唯一的保存关联对象的哈希表 AssociationsHashMap
  • 使用 DISGUISE(object) 作为 key 寻找对应的 ObjectAssociationMap
  • 如果找到了对应的 ObjectAssociationMap,遍历其成员对应的 key 是否存在,如果存在 key,获取 ObjcAssociation
  • 返回关联对象 ObjcAssociation 的值。

objc_removeAssociatedObjects

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

_object_remove_assocations
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());
}
复制代码


  • 获取唯一的保存关联对象的哈希表 AssociationsHashMap
  • 如果哈希表 AssociationsHashMapsize0,直接 return
  • 使用 DISGUISE(object) 作为 key 寻找对应的 ObjectAssociationMap
  • 如果找到了对应的 ObjectAssociationMap,遍历其成员对应的 key 是否存在,如果存在 key,然后将所有的关联结构保存到 vector 中。
  • 删除关联关系,释放关联对象。