什么是关联引用,关联对象?
官方给出的解释是:关联引用模拟了将对象实例变量添加到现有类中。使用关联引用,可以在不修改类声明的情况下将存储的数据添加到对象。如果您无权访问类的源代码,或者由于二进制兼容性原因而无法更改该对象的布局,使用关联引用则可以解决这些问题。
关联引用基于密钥。对于任何对象,您都可以根据需要添加任意数量的关联,每个关联都使用不同的键key。关联还可以确保关联的对象至少在源对象的生命周期内保持有效。
关联对象并不是存储在被关联对象本身的内存中,通过分析底层实现,它存储在由AssociationsManager管理的全局统一的一个AssociationsHashMap中。
关联对象有以下4个核心内容:
AssociationsManager: 关联对象的管理类AssociationsHashMap: 存储关联对象的HashMapObjectAssociationMap: 存储确定对象的表ObjecAssociation: 表中存储的最小结构,存储值和策略
关联对象主要有3个API:
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}
void objc_removeAssociatedObjects(id object)
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object);
}
}
objc_setAssociatedObject是设置关联对象objc_getAssociatedObject是获取关联对象objc_removeAssociatedObjects是移除关联对象
通过这3个方法,我们就可以实现对对象添加关联,并对关联进行相关的操作。
那么我们先来具体看一下,这3个方法都做了什么:
objc_setAssociatedObject
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
if (!object && !value) return;
......
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
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).
if (old_association.hasValue()) ReleaseValue()(old_association);
}
该方法需要的参数:
id object: 需要关联的对象void *key: 给关联的表中设置的keyid value: 关联的属性uintptr_t policy: 关联策略
关联策略的选项如下:
OBJC_ASSOCIATION_ASSIGN = 0: 弱引用OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1: 强引用,且nonatomicOBJC_ASSOCIATION_COPY_NONATOMIC = 3:copy、nonatomicOBJC_ASSOCIATION_RETAIN = 01401: 强引用,且atomicOBJC_ASSOCIATION_COPY = 01403:copy、atomic
通过以上的代码,我们可以看出AssociationsManager管理着所有的AssociationsHashMap,首先判断传入的value是否是nil,
- 如果是
nil,则看能否找到object表对应的ObjectAssociationMap,找到的话就把原来存的值置空。(解除关联) - 如果不是
nil,则通过传入的object找到对应的表,- 如果找到,然后通过传入的
key来找对应的值,这个值是一个map,存着value和策略- 如果找到就对其进行处理,设置新值
- 如果找不到,就创建一个新的
map,存入value和策略。
- 如果找不到表就说明没有这张表,那就创建一张新表,然后再创建一个新的
map,存入value和策略。
- 如果找到,然后通过传入的
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());
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) {
objc_retain(value);
}
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
objc_autorelease(value);
}
return value;
}
该方法需要的参数如下:
id object: 需要关联的对象void *key: 给关联的表中设置的key
同理,先通过AssociationsManager拿到存放所有hash的AssociationsHashMap,然后根据传入的object生成的地址找到相关的ObjectAssociationMap表,
- 如果找到,然后通过传入的
key来找对应的ObjcAssociation- 如果找到就对其进行处理,然后返回
value - 如果找不到,就返回
nil。
- 如果找到就对其进行处理,然后返回
- 如果找不到,则返回
nil。
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());
}
该方法只需要传入一个参数:id object,为需要删除关联的对象。
同理,先通过AssociationsManager拿到存放所有hash的AssociationsHashMap,然后根据传入的object生成的地址找到相关的ObjectAssociationMap表,然后将该表中的值存到一个临时变量elements中,该表中的所有值。最后释放elements中的数据。
了解了关联对象,我们来看看它有什么具体的用处。
给分类添加属性
首先我们创建一个TPerson类:
@interface TPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
#import "TPerson.h"
@implementation TPerson
+ (void)load {
NSLog(@"类-load");
}
@end
并且为其创建一个分类:
#import "TPerson.h"
@interface TPerson (addition)
@property (nonatomic, copy) NSString *cateProp;
@end
@implementation TPerson (addition)
+ (void)load {
NSLog(@"分类-load");
}
@end
如果给TPerson添加在TPerson的分类中添加了一个cateProp的字符串属性,当我们在main函数中运行如下代码,就会崩溃:
TPerson *per = [TPerson alloc];
per.cateProp = @"分类的属性";
NSLog(@"---%@----", per.cateProp);
-[TPerson setCateProp:]: unrecognized selector sent to instance提示我们TPerson没有为cateProp实现set方法,那么我们如何实现它的set/get方法呢?
使用关联引用,我们就可以实现set/get方法:
- (void)setCateProp:(NSString *)cateProp {
objc_setAssociatedObject(self, @"cateProp", cateProp, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cateProp {
return objc_getAssociatedObject(self, @"cateProp");
}
这样分类的cateProp属性就可以正常使用了。
总结
