iOS-底层原理iOS面试

iOS 关联对象 objc_setAssociatedObjec

2020-03-14  本文已影响0人  孙掌门

iOS 关联对象 objc_setAssociatedObject ,从源码探讨原理,以及释放时机

1.objc_setAssociatedObject

直接上源码,当我们在 OC 层调用set方法的时候,源码直接会调用另个方法


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


void _object_set_associative_reference(id object, 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;
    
    assert(object);
    
    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));
    
    // retain the new value (if any) outside the lock.
    ObjcAssociation old_association(0, nil);
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_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);
                }
            } else {
                // create the new association (first time).
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                object->setHasAssociatedObjects();
            }
        } else {
            // setting the association to nil breaks the association.
            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);
                }
            }
        }
    }
    // release the old value (outside of the lock).
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

下面我们来分析下源码

首先


id new_value = value ? acquireValue(value, policy) : nil;

会调用


static id acquireValue(id value, uintptr_t policy) {
    switch (policy & 0xFF) {
    case OBJC_ASSOCIATION_SETTER_RETAIN:
        return objc_retain(value);
    case OBJC_ASSOCIATION_SETTER_COPY:
        return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
    }
    return value;
}

是来判断当前的策略的,就是我们外面会穿进来一个参数,用来标识是引用计数加一还是copy,接着


AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);

接着拿着 AssociationsManager


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

取出 hashMap,也就是 AssociationsHashMap disguised_ptr_t disguised_object = DISGUISE(object); 拿到当前对象的补码,计算机能够识别的补码,跟着思路继续往下走,看代码


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);
                }
            } else {
                // create the new association (first time).
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                object->setHasAssociatedObjects();
            }

这段代码什么意思呢?我们上边拿到了全局的 AssociationsHashMap 这里面是以每个对象的指针按照16位取反得到补码,作为键,然后以ObjectAssociationMap作为value存储的,那么ObjectAssociationMap又是什么呢,


AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // secondary table exists
                ObjectAssociationMap *refs = i->second;

这段代码是通过补码找到 ObjectAssociationMap ,然后,


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

从这段代码我们可以猜到,ObjectAssociationMap 中,是以传入的key作为key,然后以ObjcAssociation作为 value,而 ObjcAssociation 存储的是策略和我们的value,注意我们的value在上面处理过,进行过引用计数加一或者copy操作。源码这个额地方做的就是如果没有就创建一个存进去,如果有就做一下替换。如果看不明白,就从源码的 else 语句,一看便知

else {
                // create the new association (first time).
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                object->setHasAssociatedObjects();
            }

如果从我们的 hashmap 中没有找到 ,那么久新创建一个 ObjectAssociationMap ,然后当前全局的 hashMap 的 key为当前对象指针做的补码,你可以理解为以当前对象作为key,然后 以新建的这个 map作为value,然后,这个mao,又以当前的传进来的 key 作为key,以ObjcAssociation作为value,ObjcAssociation 存储着策略和key对应的value,也是传进来的。

不要忽略一个逻辑,就是上面的 如果 new_value 为 nil,


else {
            // setting the association to nil breaks the association.
            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);
                }
            }


如果传进来的值为 nil,就会找到对应的 map,然后找到对应的 association 清除,所以说,如果我们给我们的关联对象设置为nil,就就是做了清除操作

我们再来看下 AssociationsManager,

class AssociationsManager {
    // associative references: object pointer -> PtrPtrHashMap.
    static AssociationsHashMap *_map;
public:
    AssociationsManager()   { AssociationsManagerLock.lock(); }
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }
    
    AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

AssociationsManager 是全局唯一的,一次只能有一个manager,后面可以知道,里面有一个静态指针 AssociationsHashMap,全局只有一份,程序已启动就会 有一份,无论我们初始化了几个manager,然后我们看他的构造函数和析构函数,是做了一个锁,来保证 manager 全局只有一份,初始化的时候加锁了,然后在想初始化是不行的,只能等析构函数,系统应该是为了保证唯一性,也就是说我们如果正在设置关联对象,这时候又来设置是会被锁拦截的。

1.1总结


1. 首先根据传进来的关联策略,来对传进来的 value 进行处理,策略也是我们穿进去的,是否进行copy
2. 然后初始化全局 `AssociationsManager`,上面分析过我们的manager,如果这时候有另一个对象来初始化做关联对象,那么他是会被阻塞的,阻塞在构造函数,当我们这次初始化完成,调用了析构函数,下一次才会进行。
3. 然后取出manager中的全局唯一的一份 hashMap
4. 然后调用函数,获取当前对象的反码
5. 然后判断根据我们传进来的value和策略生成的新value是否存在
6. 如果存在value,从 hashMap 中,根据反码作为 key ,取出 ObjectAssociationMap
7. 如果找到 map,然后根据我们传入的 key 作为 key、,找到 ObjcAssociation对象
8. 如果 7 找到了 ObjcAssociation,那么就用新值替换旧的值
9. 如果 7 没有找到,就新建一个 ObjcAssociation 对象,以我们的key和value构造,然后以传进来的key,存储到 ObjectAssociationMap 中
10. 如果 6 没有找到,ObjectAssociationMap,就会新建一个 ObjectAssociationMap ,然后根据我们传进来的 key 和 value 生成新的ObjcAssociation对象,然后存储进去
11. 如果5中发现我们传进来的value为nil,就一步步找,如果找到了存储的ObjcAssociation,就将其销毁

2._object_get_associative_reference



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) {
                    objc_retain(value);
                }
            }
        }
    }
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        objc_autorelease(value);
    }
    return value;
}

上面的代码我就不分析了,因为前面已经分析过set,get很简单,只是,需要根据我们设置的策略判断是否需要retail和release

3.释放时机

前面我们已经讲了set 和 get ,那么重头戏就是,关联对象什么时候释放呢?如果你看过我前面的 weak 释放时机 ,你就会明白的,你可以想想一下,我们只有设置关联属性,什么时候去释放过他,那么系统是怎么做的呢?

从我们观看源码可以知道,在我们的对象进行 dealloc 的时候,

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

回去判断我们的 isa 指针是否有弱引用,关联对象等一系列乱七八糟的东西,isa 指针 在我之前的文章也讲过,所以,当我们有关联对象的时候,会执行object_dispose((id)this);f方法,


object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}


void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

可以看到,有这么一句 if (assoc) _object_remove_assocations(obj); 这个就是自动释放我们的关联对象,


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


代码很容易理解,就是找到 map ,然后循环遍历释放我们设置过得 key value,是不是说到这,豁然开朗了。

4.加餐

上面我们说到过,是根据 isa 指针里面的 字段来判断是否设置过关联对象的,那么是咋么设置到 isa 指针里面的呢?

我们在上面有一个流程是这样的,若果没有 map,会创建map


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

赋值完之后,会调用 object->setHasAssociatedObjects();,


inline void
objc_object::setHasAssociatedObjects()
{
    if (isTaggedPointer()) return;

 retry:
    isa_t oldisa = LoadExclusive(&isa.bits);
    isa_t newisa = oldisa;
    if (!newisa.nonpointer  ||  newisa.has_assoc) {
        ClearExclusive(&isa.bits);
        return;
    }
    newisa.has_assoc = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}

设么是 TaggedPointer,我之前文章也讲过,这个地方就是将我们的 isa 指针的 has_assoc 置为1,一直到声明周期结束,我们在dealloc的时候,就是根据这个字段来判断的,是不是又豁然开朗了.

上一篇 下一篇

猜你喜欢

热点阅读