Associated Object 源码解析

1,185 阅读5分钟

我们都知道,在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
  • 最后,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大神的博客:

Associated Objects

中,他对关联属性的使用场景如下:

  1. Adding private variables to facilitate implementation details(添加一些私有的便利的方法实现)
  2. Adding public properties to configure category behavior.(添加一些共有的属性来配置这个分类)
  3. Creating an associated observer for KVO(在KVO中创建一个关联的观察者)

当然他也提到了,我们不应该在如下的场景中使用关联属性:

  1. Storing an associated object, when the value is not needed(当这个值不再需要的时候,我们不该存储这个关联对象)

  2. Storing an associated object, when the value can be inferred(当这个值可以被推断出来的时候,我们不该使用关联对象)

  3. Using associated objects instead of X,如:

    Subclassing for when inheritance is a more reasonable fit than composition.(当继承比组合更合理的情况使用关联对象)