我们都知道,在NSObject对象中,我们可以创建一个category,来对object做一些辅助性质的工作,比如代码的结偶啊等等,可以动态的为对象添加一些新的行为。那么他们是怎么实现的呢,那么我们就看看runtime的源码中,关于associated object是如何实现的吧。
首先我们可以创建一个NSObject的Category,然后动态的添加一个associatedObject的属性:
// NSObject+Associated.h
@interface NSObject (Associated)
@property (nonatomic, strong) id associatedObject;
@end
// NSObject+Associated.m
@implementation NSObject (Associated)
@dynamic associatedObject;
+ (void)load {
NSLog(@"bbb...");
}
- (void)setAssociatedObject:(id)object {
objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
return objc_getAssociatedObject(self, @selector(associatedObject));
}
然后我么就可以给associatedObject赋值了:
NSObject *object = [NSObject new];
object.associatedObject = [NSObject object];
这样我们就完成了一个常见的associated property的调用。
源码分析
objc_setAssociatedObject分析
我们看一下,objc_setAssociatedObject都经历了什么吧。
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
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);
/**
// 创建一个符合policy规则的值, 符合的值为:
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1
OBJC_ASSOCIATION_COPY_NONATOMIC = 3
剩下的值都为assign
*/
id new_value = value ? acquireValue(value, policy) : nil;
{
// 创建AssociationsManager对象
AssociationsManager manager;
// 初始化manager中的_map
AssociationsHashMap &associations(manager.associations());
// 对object的地址按位取反, 作为_map的key
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()) {
// 从_map中找,associtates是否有key值为disguised_object的对象,即ObjectAssociationMap
ObjectAssociationMap *refs = i->second;
// 从ObjectAssociationMap中找key对应的ObjcAssociation,如果有的话,则替换,没有就新建
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).
// 将这个类放入_map中,生成一个ObjectAssociationMap, 即_map[disguised_object] = refs
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
// 将key与value对应
(*refs)[key] = ObjcAssociation(policy, new_value);
// 将has_assoc置为true
object->setHasAssociatedObjects();
}
} else {
// setting the association to nil breaks the association.
// 如果传入的object = nil,则擦除这个key对应的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).
// 释放old value
if (old_association.hasValue()) ReleaseValue()(old_association);
}
从源码我们可以看到,void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy)
的流程如下:
-
首先获取判断value是否为空,如果不为空, 那么执行acquireValue方法,获取一个新的值
-
然后生成一个AssociationsManager,并生成一个associations,对associtaed property进行全局管理
-
然后判断newValue是否为空:
- 如果不为空,则去associations中查找,是否存在这个object对应的ObjectAssociationMap,
- 如果没有的话,则生成一个新的,并将它与object的DISGUISE(object)方法生成的key进行绑定,并将key与生成的ObjcAssociation(policy, new_value)对象进行绑定,然后将object中的has_assoc字段置为true,表示这个对象已经有了associated object。
- 如果已经存在的话,那么直接从_map中读取这个object对应的所有associated property,然后去查找,这个hashMap中是否存在对应的associated对象,如果存在,则先获取之前的对象,(因为最后要释放这个对象),然后赋予它一个新的值。否者直接赋值。
- 如果为空的话,那么则擦除这个key所对应的值,并获取之前的这个key所对应的value
- 如果不为空,则去associations中查找,是否存在这个object对应的ObjectAssociationMap,
-
最后,release之前的old value
从源码我们可以看到,objc_setAssociatedObject方法还是比较简单清晰的,就是创建一张全局的关联属性的表,一个对象,对应一个ObjectAssociationMap,然后将传入的key与value传递给他们,当然,需要进行一些判断,比如这个value的policy是什么,传入的值是否为nil等等,但是总体流程还是非常清晰的。
objc_getAssociatedObject
我们看这个方法的函数定义如下:
id objc_getAssociatedObject(id object, const void *key)
是不是其实就是说,从一个hashMap中,找到key对应的value呢。有了上面的对于objc_setAssociatedObject的分析,我们直接看源码注释,应该理解上就没什么问题了:
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);
/**
// 创建一个符合policy规则的值, 符合的值为:
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1
OBJC_ASSOCIATION_COPY_NONATOMIC = 3
剩下的值都为assign
*/
id new_value = value ? acquireValue(value, policy) : nil;
{
// 创建AssociationsManager对象
AssociationsManager manager;
// 初始化manager中的_map
AssociationsHashMap &associations(manager.associations());
// 对object的地址按位取反
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()) {
// 从_map中找,associtates是否有key值为disguised_object的对象,即ObjectAssociationMap
ObjectAssociationMap *refs = i->second;
// 从ObjectAssociationMap中找key对应的ObjcAssociation,如果有的话,则替换,没有就新建
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).
// 将这个类放入_map中,生成一个ObjectAssociationMap, 即_map[disguised_object] = refs
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
// 将key与value对应
(*refs)[key] = ObjcAssociation(policy, new_value);
// 将has_assoc置为true
object->setHasAssociatedObjects();
}
} else {
// setting the association to nil breaks the association.
// 如果传入的object = nil,则擦除这个key对应的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).
// 释放old value
if (old_association.hasValue()) ReleaseValue()(old_association);
}
总结
在Mattt大神的博客:
中,他对关联属性的使用场景如下:
- Adding private variables to facilitate implementation details(添加一些私有的便利的方法实现)
- Adding public properties to configure category behavior.(添加一些共有的属性来配置这个分类)
- Creating an associated observer for KVO(在KVO中创建一个关联的观察者)
当然他也提到了,我们不应该在如下的场景中使用关联属性:
-
Storing an associated object, when the value is not needed(当这个值不再需要的时候,我们不该存储这个关联对象)
-
Storing an associated object, when the value can be inferred(当这个值可以被推断出来的时候,我们不该使用关联对象)
-
Using associated objects instead of X,如:
Subclassing for when inheritance is a more reasonable fit than composition.(当继承比组合更合理的情况使用关联对象)