前言

关联对象(AssociatedObject)是一种运用runtime在分类中添加"属性"的方法。那么关联是怎样实现添加"属性"的呢?

关联对象的实现步骤

关联对象的实现步骤如下:

  1. 在分类中.h中声明一个属性
  2. 在.m中声明该属性为动态加载 @dynamic
  3. 实现setter和getter方法
1.

@interface UIViewController (Test)

@property (nonatomic, copy) NSString *es_title;

@end

@implementation UIViewController (Test)

2.

@dynamic es_title;

3.

- (void)setEs_title:(NSString *)es_title {
    objc_setAssociatedObject(self, &kTestProperty, es_title, OBJC_ASSOCIATION_COPY);
}

- (NSString *)es_title {
    return objc_getAssociatedObject(self, &kTestProperty);
}

@end

关联对象分析

实际上从上代码能看出,实际上关联对象并没有往原有类中添加成员变量。当然了,实际上类的空间编译的时候就已经是确定好的了,分类的属性实际上就是set和get方法的实现。

那么我们只要看懂set和get方法内部的实现就能搞清楚究竟关联对象是怎么做的。

源码分析

objc_setAssociatedObject

老规矩,看下runtime源码里面是怎么做的

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
    _object_set_associative_reference(object, (void *)key, value, policy);
}

跟随代码进入_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 old_association(0, nil);
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        //初始化manager,全局管理类
        AssociationsManager manager;
        //获取管理的hashmap
        AssociationsHashMap &associations(manager.associations());
        //对象地址按位取反作为地址key
        disguised_ptr_t disguised_object = DISGUISE(object);
        
        //存在关联值
        if (new_value) {
            //根据地址key在hashmap中找到存储的对象关联表(ObjectAssociationMap)
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            //若是能找到对象关联表的话
            if (i != associations.end()) {
                //获取找到的对象关联表(ObjectAssociationMap)
                ObjectAssociationMap *refs = i->second;
                //在对象关联表中根据key查找关联对象(ObjcAssociation)
                ObjectAssociationMap::iterator j = refs->find(key);
                //若能找到关联对象
                if (j != refs->end()) {
                    //存储过去的关联对象
                    old_association = j->second;
                    //将对象关联表中的关联对象重新赋值
                    j->second = ObjcAssociation(policy, new_value);
                    
                }
                //找不到关联对象
                else {
                    //根据指针key直接添加新的关联对象
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            }
            //若是找不到的话
            else {
                //创建新的对象关联map
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                //在全局管理的hashmap中添加对象关联map
                associations[disguised_object] = refs;
                //对象关联map添加新的关联对象
                (*refs)[key] = ObjcAssociation(policy, new_value);
                //标记对象存在关联对象
                object->setHasAssociatedObjects();
            }
        }
        //不存在关联值就擦除
        else {
            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); //擦除
                }
            }
        }
    }
    // 若是存在旧的关联对象,则将旧的关联对象释放
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

上面的代码把每个步骤所做的内容都解释了一遍,从头往下看的话应该能看出来。

总结

关联对象的实质是在全局管理一个map,然后用对象按位取反来作为key来存储。用一张图来说明的话,可能更加清晰

iOS关联对象源码解析

关联对象

  1. 通过AssociationsManager管理一个hashmap
  2. hashmap中用DISGUISE(obj)存储一个关联对象map(ObjetAssociationMap)
  3. 关联对象map中根据用户自定义的key存储关联对象
  4. 关联对象中包含关联策略和key

相关文章: