void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
id objc_getAssociatedObject(id object, const void *key)
void objc_removeAssociatedObjects(id object)
一对 set/get 方法和一个移除全部关联对象的方法,下面来逐个分析。
Set Associated Object
objc_setAssociatedObject()方法内部调用了_object_set_associative_reference(),将参数透传。_object_set_associative_reference的实现在 objc-references.mm 文件中。该文件包含了 ObjC 关联对象的相关实现,ObjcAssociation类是对关联对象的封装,包括关联策略(policy)以及 value 、初始化方法以及一些内部方法,这里包括根据不同关联策略对 value 的处理。AssociationsManager主要用来管理 Association Map 及相关的锁(spinlock_t)操作。
void _object_set_associative_reference(id object, constvoid *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; ... }
{ 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)); ... }
... if (value) { auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{}); if (refs_result.second) { /* it's the first association we make */ isFirstAssociation = true; }
/* establish or replace the association */ auto &refs = refs_result.first->second; auto result = refs.try_emplace(key, std::move(association)); if (!result.second) { association.swap(result.first->second); } } else { auto refs_it = associations.find(disguised); if (refs_it != associations.end()) { auto &refs = refs_it->second; auto it = refs.find(key); if (it != refs.end()) { association.swap(it->second); refs.erase(it); if (refs.size() == 0) { associations.erase(refs_it); } } } } ...
try_emplace() 方法特点是如果 container 中存在与参数相等的 key ,则直接返回,否则插入 pair 。因此如果返回值的 second 不为空即证明是第一次插入。如果要更新 value 的话,则需要手动 swap 一次。
如果 value 为 nil ,通过二次遍历找到 key 对应的 old value 后将其移除,如果在移除后该对象没有任何关联对象,则 associations 会将整个 ObjcAssociation 对象移除。
{ AssociationsManager manager; AssociationsHashMap &associations(manager.get()); AssociationsHashMap::iterator i = associations.find((objc_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(); } }
typedefOBJC_ENUM(uintptr_t, objc_AssociationPolicy){ OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */ OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied. OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object. OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied. };