runtime源码探究(三)属性关联

本文深入探讨了Objective-C中objc_setAssociatedObject、objc_getAssociatedObject和objc_remove_associations的实现原理。讲解了属性关联如何在运行时动态添加对象属性,以及如何存储和检索关联值,包括对象的内存管理策略。同时指出在对象析构时,Apple自动处理了关联引用的释放。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

_object_set_associative_reference函数首先初始化了一个ObjcAssociation对象

 ObjcAssociation old_association(0, nil)

old_association用来存储关联的值和关联策略,默认是OBJC_ASSOCIATION_ASSIGN。接下来如果发现传入的value不为空,那么就对传入的value进retain或者copy操作(如果关联策略是OBJC_ASSOCIATION_SETTER_RETAIN或者OBJC_ASSOCIATION_SETTER_COPY)

id new_value = value ? acquireValue(value, policy) : nil;
//acquireValue函数的实现
static id acquireValue(id value, uintptr_t policy) {
    switch (policy & 0xFF) {
    case OBJC_ASSOCIATION_SETTER_RETAIN:
        return ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
    case OBJC_ASSOCIATION_SETTER_COPY:
        return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
    }
    return value;
}

接下来初始化AssociationsManager,AssociationsManager里面主要是存在一个hash map,结构如下:

class AssociationsManager {
    static spinlock_t _lock;
    static AssociationsHashMap *_map;               // associative references:  object pointer -> PtrPtrHashMap.
public:
    AssociationsManager()   { _lock.lock(); }
    ~AssociationsManager()  { _lock.unlock(); }

    AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

并且在初始化的时候获取一把自旋锁,对象析构的时候释放锁。AssociationsManager类结构里边的锁对象和map对象都是静态的,以便下次直接获取,也就是说是全局唯一的。接下来获取map对象

AssociationsHashMap &associations(manager.associations());
//把对象转成unsigned long类型数据,获取一个索引
disguised_ptr_t disguised_object = DISGUISE(object);

接下里判断如果传入的value不为空,那么就通过disguised_object从AssociationsHashMap中取出储存被关联的对象的ObjectAssociationMap,ObjectAssociationMap中存储的是该对象被关联所有的value。接下来通过传入的key看对象有没有关联过这个value,如果有就直接赋值,如果没有就创建一个。

 // break any existing association.
 AssociationsHashMap::iterator i = associations.find(disguised_object);
 if (i != associations.end()) {
            // secondary table exists
          ObjectAssociationMap *refs = i->second;
          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);
  }

如果AssociationsHashMap从没有对象的关联信息表,那么就创建一个map并通过传入的key把value存进去

ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();

如果传入的value是nil,并且之前使用相同的key存储过关联对象,那么就把这个关联的value移除(这也是为什么传入nil对象能够把对象的关联value移除)

最后把之前使用传入的这个key存储的关联的value释放(OBJC_ASSOCIATION_SETTER_RETAIN策略存储的)

 // release the old value (outside of the lock).
    if (old_association.hasValue()) ReleaseValue()(old_association);

objc_getAssociatedObject

objc_getAssociatedObject用来获取关联的value

id _object_get_associative_reference(id object, void *key) {
    id value = nil;
    uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
        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()) {
                ObjcAssociation &entry = j->second;
                value = entry.value();
                policy = entry.policy();
                if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
            }
        }
    }
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        ((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
    }
    return value;
}

实现也比较简单,还是通过对应的key在map中取出value返回,这里不再赘述。

objc_remove_assocations

移除对象的关联引用通过_object_remove_assocation函数实现,这里主要是取出对象的关联引用map,然后依次对其中的对象发送release消息(前提是关联的策略是copy或者retain)。具体实现如下:

void _object_remove_assocations(id object) {
    vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        if (associations.size() == 0) return;
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            // copy all of the associations that need to be removed.
            ObjectAssociationMap *refs = i->second;
            for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                elements.push_back(j->second);
            }
            // remove the secondary table.
            delete refs;
            associations.erase(i);
        }
    }
    // the calls to releaseValue() happen outside of the lock.
    for_each(elements.begin(), elements.end(), ReleaseValue());
}

我们可以看到,AssociationsHashMap先是先是移除了对象的ObjectAssociationMap

// copy all of the associations that need to be removed.
            ObjectAssociationMap *refs = i->second;
            for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                elements.push_back(j->second);
            }
            // remove the secondary table.
            delete refs;
            associations.erase(i);

最后通过for_each(elements.begin(), elements.end(), ReleaseValue()); 来释放每一个对象。其中ReleaseValue重载了运算符(),具体release的过程如下:

struct ReleaseValue {
    void operator() (ObjcAssociation &association) {
        releaseValue(association.value(), association.policy());
    }
};
static void releaseValue(id value, uintptr_t policy) {
    if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) {
        ((id(*)(id, SEL))objc_msgSend)(value, SEL_release);
    }
}

在实际开发中其实我们并不需要手动调用objc_remove_assocations函数,apple在dealloc中自动帮我们调用了该函数。再回顾一下dealloc的过程,dealloc主要调用了void *objc_destructInstance(id obj)函数,该函数中有这样一行代码:

if (isa->instancesHaveAssociatedObjects()) {
            _object_remove_assocations(obj);
        }

apple就是在这里释放了对象的关联引用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值