什么是关联对象?在苹果的官方文档中是这么介绍的
Associative references, available starting in OS X v10.6, simulate the addition of object instance variables to an existing class. Using associative references, you can add storage to an object without modifying the class declaration. This may be useful if you do not have access to the source code for the class, or if for binary-compatibility reasons you cannot alter the layout of the object.
Associations are based on a key. For any object you can add as many associations as you want, each using a different key. An association can also ensure that the associated object remains valid for at least the lifetime of the source object.
从OS X v10.6开始可用的关联引用模拟了将对象实例变量添加到现有类中。使用关联引用,可以在不修改类声明的情况下将存储添加到对象。如果您无权访问该类的源代码,或者由于二进制兼容性原因而无法更改该对象的布局,则这可能很有用。
关联基于密钥。对于任何对象,您都可以根据需要添加任意数量的关联,每个关联都使用不同的键。关联还可以确保关联的对象至少在源对象的生存期内保持有效。
关联对象底层原理
我们分别从以下三个方法中来探究
objc_setAssociatedObject
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
它的底层还封装了一层,我们继续往下看 其中的代码太长我们一步一步来看
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));
- 判断对象和值是否都为nil
- 通过对象的isa判断对象是否支持关联对象
ObjcAssociation old_association(0, nil);
id new_value = value ? acquireValue(value, policy) : nil;
在锁之外保留新值(如果有)。 下面的代码是关联对象的核心逻辑
// 关联对象的管理类
AssociationsManager manager;
// 获取关联的 HashMap -> 存储当前关联对象(可以理解为总表)
AssociationsHashMap &associations(manager.associations());
// 对当前的对象的地址做按位去反操作 - 就是 HashMap 的key (哈希函数),通过这个key可以找到关联对象的表
disguised_ptr_t disguised_object = DISGUISE(object);
如果new_value存在,则进入下面这段逻辑
// 获取 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();
}
如果new_value不存在,则进入下面这段逻辑
// 如果传入的value是nil,并且之前使用相同的key存储过关联对象,
// 那么就把这个关联的value移除(这也是为什么传入nil对象能够把对象的关联value移除)
// 找到这个对象的t迭代器
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
//如果j不是最后一个,那么证明这个key对一个的地方之前存过值,那么久执行清空操作
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);
}
}
// 最后把之前使用传入的这个key存储的关联的value释放(OBJC_ASSOCIATION_SETTER_RETAIN策略存储的)
if (old_association.hasValue()) ReleaseValue()(old_association);
释放old_association
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;
}
- 获取
AssociationsManager - 根据
object生成地址 - 根据生成的新地址查找出该对象的迭代器
- 根据我们外界传入的key查找出
ObjcAssociation - 把值和存储策略从
ObjcAssociation中查找出来,并根据策略对值做相应的处理 - 返回查找到的值
objc_removeAssociatedObjects
void objc_removeAssociatedObjects(id object)
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object);
}
}
如果对象存在,并且有关联对象的标识,那么则进行移除操作,同样它的底层也进行了一次封装。
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());
}
- 获取总表
- 根据对象生成相应的索引
- 根据生成的索引查找到该对象的迭代器
- 把读取出来的值和策略存储到临时的
elements中 - 清空与该对象相关的所有小表
- 循环释放所有的值
总结
- 通过关联对象,我们可以给一个对象添加新的属性
- 通过给
objc_setAssociatedObject传值为nil,可以清空这个属性的值,只是清空了这个属性的值。 objc_removeAssociatedObjects可以清空这个对象的所有关联对象的值