苹果是如何实现关联对象的

114 阅读4分钟

为了保持 ABI 的稳定,使用分类时不允许为主类增加实例变量。但苹果也提供了关联对象,让我们作为在分类中为主类增加实例变量的一种替代,今天我们将会探究苹果时如何在运行时实现这一技术的。

设置关联对象

苹果提供的设置变量的 API 如下:

// 为一个给定的对象(object)根据指定的 Key 和关联策略(association policy)设置值(value)
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

其中,object 指的是要给哪个对象设置关联值。key 指的是关联对象的 keyvalue 指的是与 key 相匹配的 value,传 nil 会清除现有的关联对象。policy 指的是关联对象的内存管理策略。

其源码实现如下:

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

void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    // This code used to work when nil was passed for object and key. Some code
    // probably relies on that to not crash. Check and handle it explicitly.
    // rdar://problem/44094390
    if (!object && !value) return; // object 和 value 都为 nil 的时候,直接返回。

    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));

    DisguisedPtr<objc_object> disguised{(objc_object *)object}; // 根据 object,生成对应的 DisguisedPtr 对象。
    ObjcAssociation association{policy, value}; // 根据 policy 和 value 生成 ObjcAssociation 对象。

    // retain the new value (if any) outside the lock.
    association.acquireValue();

    bool isFirstAssociation = false;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get()); // 获取 AssociationsHashMap 对象。

        if (value) { // 如果 value 不为空
        // 1、try_emplace: 如果 objc 查找到对应的 bucket(ObjectAssociationMap),会返回 second 为 true。否则返回的 second 为 false (其中会在对应 key 的地方插入一个空的 bucket(ObjectAssociationMap),并且返回这个 bucket 的地址)
        // 2. 在返回的 bucket (ObjectAssociationMap 类型也是一个Densemap)中。然后将 key,value 插入到 Densemap 中(也是如果存在就插入,不足在就创建新的,然后在返回的为 false 的情况下,在数据的指针地址修改数据)
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{}); // 根据 object 查找是否存在关联引用。
            if (refs_result.second) { // second 为 YES,代表是第一次插入
                /* it's the first association we make */
                isFirstAssociation = true;
            }

            /* establish or replace the association */
            auto &refs = refs_result.first->second; // 取到 objc 对应的 bucket 的地址
            auto result = refs.try_emplace(key, std::move(association)); // 找到存储 bucket 也是一个 DenseMap,将 key,association(包含value) 插入到 bucket 中。
            if (!result.second) {
                association.swap(result.first->second); // 如果 try_emplace 没有查找到 key,就创建新的 bucket ,然后返回 false, 然后有新的 bucket 的地址。然后使用 swap 修改这个地址的数据。 
            }
        } else { // 如果 value 为空
            auto refs_it = associations.find(disguised); // 根据 objc 查找对应的 bucket(也是一个 DenseMap)。find 找不到,会返回 end()。
            if (refs_it != associations.end()) { 
                auto &refs = refs_it->second; // 取到 bucket 的地址,修改地址中的数据。refs 是 objc 对应的 bucket。
                auto it = refs.find(key); // objc 对应 bucket 也是一个 densemap 然后根据 key 查找。找不到返回 end。
                if (it != refs.end()) {
                    association.swap(it->second); // 如果找到, refs 要清楚 key 对应的数据。删除内存。
                    refs.erase(it);
                    if (refs.size() == 0) { // 如果 objc 对应的 bucket 中的数量是0,将这个 bucket 也清掉。
                        associations.erase(refs_it);

                    }
                }
            }
        }
    }

    // Call setHasAssociatedObjects outside the lock, since this
    // will call the object's _noteAssociatedObjects method if it
    // has one, and this may trigger +initialize which might do
    // arbitrary stuff, including setting more associated objects.
    if (isFirstAssociation) // 如果是第一个关联对象,为对象设置是一个关联对象的标识位。
        object->setHasAssociatedObjects();

    // release the old value (outside of the lock).
    association.releaseHeldValue(); // 释放旧值。
}

这其中有几个关键的数据结构需要讲一下:

AssociationsManager:关联对象的管理类,内部有一个 Storage,是一个 DenseMapDisguisedPtr<objc_object> 对象为 key,可以简单理解为对象的地址。以 ObjectAssociationMap 对象为值。

typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;

class AssociationsManager {
    using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
    static Storage _mapStorage;

public:
    AssociationsManager()   { AssociationsManagerLock.lock(); }
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }

    AssociationsHashMap &get() {
        return _mapStorage.get();
    }

    static void init() {
        _mapStorage.init();
    }
};

ObjectAssociationMap:具体关联值的封装类,以 const void * 类型的指针为 key,即关联对象中的 key,以 ObjcAssociation 为值:

typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;

ObjcAssociation 则是封装了 valuepolice 封装的类:

class ObjcAssociation {
    uintptr_t _policy;
    id _value;
    ...

至此,设置关联对象的方法就都清楚了。

获取关联对象

设置关联对象的方法如下:

id
objc_getAssociatedObject(id object, const void *key)
{
    return _object_get_associative_reference(object, key);
}

id
_object_get_associative_reference(id object, const void *key)
{
    ObjcAssociation association{};

    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object); // 根据 object 获取对应的关联对象。
        if (i != associations.end()) { // 找到的话,根据对应的 key 返回相应的 value
            ObjectAssociationMap &refs = i->second;
            ObjectAssociationMap::iterator j = refs.find(key);
            if (j != refs.end()) {
                association = j->second;
                association.retainReturnedValue();
            }
        }
    }

    return association.autoreleaseReturnedValue();
}

移除所有的关联对象

根据 object 获取对应的 ObjectAssociationMap,并对其中内容依次进行擦除。

void objc_removeAssociatedObjects(id object) 
{
    if (object && object->hasAssociatedObjects()) { // 如果对象存在相应的关联对象,去执行移除操作
        _object_remove_assocations(object, /*deallocating*/false);
    }
}

// Unlike setting/getting an associated reference,
// this function is performance sensitive because of
// raw isa objects (such as OS Objects) that can't track
// whether they have associated objects.
void
_object_remove_assocations(id object, bool deallocating)
{
    ObjectAssociationMap refs{};

    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object); // 根据 object 获取对应的关联对象。
        if (i != associations.end()) {
            refs.swap(i->second);

            // If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
            bool didReInsert = false;
            if (!deallocating) {
                for (auto &ref: refs) {
                    if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
                        i->second.insert(ref);
                        didReInsert = true;
                    }
                }
            }
            if (!didReInsert)
                associations.erase(i);
        }
    }

    // Associations to be released after the normal ones.
    SmallVector<ObjcAssociation *, 4> laterRefs;

    // release everything (outside of the lock).
    for (auto &i: refs) {
        if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
            // If we are not deallocating, then RELEASE_LATER associations don't get released.
            if (deallocating)
                laterRefs.append(&i.second);
        } else {
            i.second.releaseHeldValue();
        }
    }
    for (auto *later: laterRefs) {
        later->releaseHeldValue();
    }
}