为了保持 ABI
的稳定,使用分类时不允许为主类增加实例变量。但苹果也提供了关联对象,让我们作为在分类中为主类增加实例变量的一种替代,今天我们将会探究苹果时如何在运行时实现这一技术的。
设置关联对象
苹果提供的设置变量的 API
如下:
// 为一个给定的对象(object)根据指定的 Key 和关联策略(association policy)设置值(value)
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
其中,object
指的是要给哪个对象设置关联值。key
指的是关联对象的 key
。value
指的是与 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
,是一个 DenseMap
以 DisguisedPtr<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
则是封装了 value
和 police
封装的类:
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();
}
}