阅读 276

关联对象的实现原理【OC】

前言

AssociationedObject多用于在Category中为特定类扩展成员变量,也有用于在运行时为某些对象动态创建成员变量。AssociationedObject可以说是一种特殊的成员变量。 这篇文章是来详细解释AssociationedObject的实现原理,篇幅较长。

相关方法

objc_AssociationPolicy

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3, 
    OBJC_ASSOCIATION_RETAIN = 01401, 
    OBJC_ASSOCIATION_COPY = 01403
};
复制代码

这是在调用objc_setAssociatedObject时需要用到的参数,用于指定关联参数的引用策略。

  • OBJC_ASSOCIATION_ASSIGN

    将关联引用描述为弱引用

  • OBJC_ASSOCIATION_RETAIN_NONATOMIC

    将关联引用描述为强引用,并且非原子性。

  • OBJC_ASSOCIATION_COPY_NONATOMIC

    将关联引用描述为拷贝引用,并且非原子性。

  • OBJC_ASSOCIATION_RETAIN

    将关联引用描述为强引用,并且为原子性。

  • OBJC_ASSOCIATION_COPY

    将关联引用表述为拷贝引用,并且为原子性。

这一段比较好理解,与Property一样,关联引用也可以设置引用描述。

objc_setAssociatedObject

OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
复制代码

创建和设置一个关联对象的方法。

将一个选定值(value)通过Key-Value的形式挂载在目标对象(object)上,同时指定关联的策略(policy),这样就能生成一个关联对象。

通过将目标对象(object)上指定的Key对应的值(value)设置nil,即可以将已存在的关联对象清除。

objc_getAssociatedObject

OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
复制代码

关联对象值的获取方法。

通过关联Key,在目标对象(object)中,获取到关联对象的值(返回值)。

objc_removeAssociatedObjects


/** 
 * Removes all associations for a given object.
 * 
 * @param object An object that maintains associated objects.
 * 
 * @note The main purpose of this function is to make it easy to return an object 
 *  to a "pristine state”. You should not use this function for general removal of
 *  associations from objects, since it also removes associations that other clients
 *  may have added to the object. Typically you should use \c objc_setAssociatedObject 
 *  with a nil value to clear an association.
 * 
 * @see objc_setAssociatedObject
 * @see objc_getAssociatedObject
 */
OBJC_EXPORT void objc_removeAssociatedObjects(id object)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
复制代码

移除目标对象(objct)的所有关联对象。

官方在这做了提示:这个方法的主要目的是为了让对象更容易返回到原始状态,调用此方法会将绑定在这个目标对象上的所有关联对象都清除。如果别的开发也为这个object设置了关联对象,或者你在别的模块中也为其设置了不同的关联对象,调用此方法后会被一并删除。通常清除关联对象,请通过objc_setAssociatedObject将关联对象设置为nil的方式来清除关联对象。

底层实现

在底层实现上,分为GC版和无GC版,GC是MacOS的垃圾回收机制,不过现在也被弃用了,推荐使用ARC。而iOS上只有MRC和ARC,因此这里只截取了无GC版的代码。

objc_setAssociatedObject

在这个方法中有很多的数据结构,首先介绍下数据结构,帮助理解,也可以先往下翻看主要流程,有看不明白的再回来查找。

AssociationsManager

// class AssociationsManager manages a lock / hash table singleton pair.
// Allocating an instance acquires the lock, and calling its assocations()
// method lazily allocates the hash table.

spinlock_t AssociationsManagerLock;

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

AssociationsHashMap *AssociationsManager::_map = NULL;

复制代码

AssociationsManager的作用管理lock,当它被创建的时候会加锁,在它被析构的时候会释放锁,同时,有一个全局变量_map,管理的是目标对象与HashMap(Key-Value都为指针)的的关系。

AssociationsManager中有一个全局变量类型为AssociationsHashMap的_map引用。

提供了一个创建AssociationsHashMap的左值引用方法,_map是引用,*_map则是解引用又变成了AssociationsHashMap对象,函数前加了&(取地址符),返回的是AssociationsHashMap对象引用地址。

同时声明了一个类型为AssociationsHashMap的全局变量引用。

AssociationsHashMap

	typedef ObjcAllocator<std::pair<const disguised_ptr_t, ObjectAssociationMap*> > AssociationsHashMapAllocator;
    class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
	};

//	unordered_map的泛型定义
    template <class _Key,
              class _Tp, 
              class _Hash = hash<_Key>,
              class _Pred = equal_to<_Key>,
              class _Alloc = allocator<pair<const _Key, _Tp> > >
复制代码

AssociationsHashMap继承自unordered_map <disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator>

解释下这个泛型的意思

  • _Key(key_type)对应disguised_ptr_t,disguised_ptr_t实际上是unsigned long类型,这是用来存放指针的。
  • _Tp(mapped_type)对应ObjectAssociationMap *,这个表明了Map中存储的值类型,后面会介绍ObjectAssociationMap。
  • _Hash(hasher)对应DisguisedPointerHash,提供hash算法。
  • _Pred(key_equal)对应的是DisguisedPointerEqual,提供equal算法(两个指针相同则相等)。
  • _Alloc(allocator_type)对应的是构造函数的类型,这里构造函数类型是ObjcAllocator<std::pair<const disguised_ptr_t, ObjectAssociationMap *> >,也是一个泛型,用std::pair实现Key是disguised_ptr_t,Value是ObjectAssociationMap *的键值对。

它实现了创建和删除的功能。

disguised_ptr_t

    typedef unsigned long		uintptr_t;

    typedef uintptr_t disguised_ptr_t;
    inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); }
    inline id UNDISGUISE(disguised_ptr_t dptr) { return id(~dptr); }
复制代码

这里可以看出disguised_ptr_t实际上是一个unsigned long,它的长度与指针相同,所以被当做指针使用。 ~uintptr_t(value):value本身也是个对象指针,将它包装成unsigned long类型,载逐位取反后返回。 id(~dptr):将disguised_ptr_t逐位取反后,返回对象(id)指针。

ObjectAssociationMap

    typedef ObjcAllocator<std::pair<void * const, ObjcAssociation> > ObjectAssociationMapAllocator;
    class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };

//	std::map的泛型定义
	template <class _Key, 
              class _Tp,
              class _Compare = less<_Key>,
          class _Allocator = allocator<pair<const _Key, _Tp> > >
复制代码

与AssociationsHashMap类似,ObjectAssociationMap继承自public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator>

泛型的意思是:

  • _key(key_type)对应的是void *,即无类型指针,表示内存地址。

  • _Tp(mapped_type)对应[ObjcAssociation](#### ObjcAssociation),这个上面提到过,用来存放关联对象的值与关联策略。

  • _Compare(key_compare)对应的是ObjectPointerLess,提供比较算法(比对指针地址)。

  • _Allocator(allocator_type)对应的是ObjcAllocator<std::pair<void * const, ObjcAssociation> >,用std::pair实现Key是void * const(内存地址),Value是ObjcAssociation的键值对。

ObjcAssociation

class ObjcAssociation {
        uintptr_t _policy;
        id _value;
    public:
        ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
        ObjcAssociation() : _policy(0), _value(nil) {}

        uintptr_t policy() const { return _policy; }
        id value() const { return _value; }
        
        bool hasValue() { return _value != nil; }
    };
复制代码

这是关联对象的结构,存储着关联策略_policy和关联对象的值_value

acquireValue

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;
}
复制代码

判断引用策略,如果为Retian的,则会调用objc_retain将value的引用计数加一,如果是Copy,则会调用Value的copy方法生成新的拷贝,其他的策略不作处理。

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;
}
复制代码

这里主要做的是将isa中的has_assoc标志位设置为true。

Tagged Pointer

这里涉及到了Tagged Pointer的概念,Tagged Pointer是一种为了节约内存占用的策略,它的原理是将指针对象的数据直接存于指针中,在64位系统中被引入。

比如,在64位系统中,一个指针为8字节,当指针关联的值小于8位的时候,系统会将指针转化成Tagged Pointer,并在最后一个bit位加入TaggedPoint标识,这个指针的结构就变成了“0x存储数据+TaggedPoint标识”的结构。

  0xb000000000000032
  |---------------|-|
0x|----存储数据----|标识|
复制代码

这么做的好处是,减少了内存的占用,又因为数据不需要放入堆中,所以不需要malloc和free,所以读取和运行速度都得到了提升。

这个时候指针已经变成了一个值,而不再是一个地址,所以它也没有isa指针,所以要先做判断。

ReleaseValue 和 releaseValue


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) {
        return objc_release(value);
    }
}

复制代码

这里是关联对象释放的相关代码,如果引用策略为Retian的话,释放时,会将引用计数减一。

主要实现

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // 创建一个 ObjcAssociation对象,初始化policy为OBJC_ASSOCIATION_ASSIGN,value为nil。
    ObjcAssociation old_association(0, nil);
    // 根据引用策略处理value。如果Copy,调用value的copy方法获取new_value,如果是Retain的,则持有value,value的引用计数加一,其他策略不作处理。
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        // 创建一个AssociationsManager,在初始化时会加锁,在析构时会解锁。
        // C++会默认为对象进行初始化,即已经加锁。
        AssociationsManager manager;
        // 获取AssociationsManager中全局变量AssociationsHashMap的引用,
        // 调用AssociationsHashMap的拷贝构造函数
        // 用上面的引用直接初始化新的associations变量。
        AssociationsHashMap &associations(manager.associations());
        // 创建一个disguised_ptr_t,调用DISGUISE(object),使用目标对象初始化。
        disguised_ptr_t disguised_object = DISGUISE(object);
        // 如果new_value 不为nil,情景是设置新的关联值、或者修改关联值。
        if (new_value) {
			// 在AssociationsHashMap中通过迭代器查找,
            // 与disguised_ptr_t相对应的ObjectAssociationMap*(i)
            // 注:map的结构为std::pair<const disguised_ptr_t, ObjectAssociationMap*>。
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            // 如果查找到Map中对应的ObjectAssociationMap*(i),即修改关联值的情况。
            // 注:在C++的map中,如果未查找到元素迭代器会返回map.end();
            if (i != associations.end()) {
				// 将ObjectAssociationMap*(refs)
                // 指向AssociationsHashMap(i)的second,
                // 这是Map的Value。
                ObjectAssociationMap *refs = i->second;
				// 在ObjectAssociationMap*(refs)中通过key,
                // 来查找对应存在的ObjectAssociationMap对象(j)。
                ObjectAssociationMap::iterator j = refs->find(key);
                // 如果查找到了对应的ObjcAssociation(j)。
                // 注:map的结构为std::pair<void * const, ObjcAssociation>
                if (j != refs->end()) {
                    // 将ObjectAssociationMap的value,
                    // 即ObjcAssociation,赋值给old_association
                    old_association = j->second;
                    // 将这个Map的Value更新为新构建的ObjcAssociation。
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    // 如果j是容器唯一的元素,
                    // 为ObjectAssociationMap的引用(*refs)设置一个Map,
                    // key为传入的key,Value为新构建的ObjcAssociation。
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // 如果Object是容器里唯一的元素
				// 创建一个ObjectAssociationMap
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                // 在AssociationsHashMap(associations)中,
                // 以Key为disguised_object存储。
                associations[disguised_object] = refs;
                // 为ObjectAssociationMap创建,Key为传入值key,
                // value为新构建的ObjcAssociation。
                (*refs)[key] = ObjcAssociation(policy, new_value);
                // 调用关联目标对象的setHasAssociatedObjects方法。
                // 将对象的isa中的has_assoc设置为true
                // 即标识这个object有关联对象存在。
                // 这个方法位于objc-objct内。
                object->setHasAssociatedObjects();
            }
        } else {
            // 当new_value = nil时,
            // 通过disguised_object查找对应的ObjectAssociationMap(i)
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            // 如果查到了对应的ObjectAssociationMap(i)
            if (i !=  associations.end()) {
                // 取出ObjectAssociationMap *refs
                ObjectAssociationMap *refs = i->second;
                // 通过传入的key查找对应的ObjcAssociation(j)
                ObjectAssociationMap::iterator j = refs->find(key);
                // 如果查到了对应的ObjcAssociation(j)
                if (j != refs->end()) {
                    // 将旧值取出,赋值给old_association。
                    old_association = j->second;
                    // 将ObjcAssociation(j)从ObjectAssociationMap中移除。
                    refs->erase(j);
                }
            }
        }
        //到这里释放锁。
    }
    // 如果存在旧值,则将旧值释放。
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

复制代码

关联对象的存储结构

看到这里可以总结一下关联关系的存储结构了。

AssociationsHashMap是管理目标对象(object)与ObjectAssociationMap的关系

ObjectAssociationMap是管理Key与ObjectAssociation(关联对象)的关系。

2019-03-14-AssociationedObject-1

objc_getAssociatedObject

下面分析的是关于关联对象的取值逻辑,大部分结构体部分在设置值部分都说了,又遇到不理解的回头去看,这里直接将主要实现了。

主要实现

id _object_get_associative_reference(id object, void *key) {
    // 创建一个value。
    id value = nil;
    // 默认引用策略为assign。
    uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
    {
        // 创建AssociationsManager并加锁。
        AssociationsManager manager;
        // 获取全局AssociationsHashMap。
        AssociationsHashMap &associations(manager.associations());
        // 通过object构建disguised_object。
        disguised_ptr_t disguised_object = DISGUISE(object);
        // 根据disguised_object查找对应的ObjectAssociationMap。
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        // 如果查到了ObjectAssociationMap。
        if (i != associations.end()) {
            // 提取ObjectAssociationMap。
            ObjectAssociationMap *refs = i->second;
            // 根据key查找对应的ObjcAssociation。
            ObjectAssociationMap::iterator j = refs->find(key);
            // 如果ObjcAssociation存在。
            if (j != refs->end()) {
                // 获取ObjcAssociation
                ObjcAssociation &entry = j->second;
                // 将value设置为ObjcAssociation的value。
                value = entry.value();
                // 将policy设置为ObjcAssociation的policy。
                policy = entry.policy();
                // 如果引用策略是Retain模式
                if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
                    // 调用value的retian方法,引用计数加一。
                    objc_retain(value);
                }
            }
        }
        //释放锁
    }
    // 如果值存在,并且引用策略是AutoRelease模式。
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        // 调用value的autorelease方法,将value设置为autorelease。
        objc_autorelease(value);
    }
    // 返回取到的关联对象
    return value;
}
复制代码

objc_retain

id 
objc_retain(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}
复制代码

在retian的时候也判断了是否为Tagged Pointer。

这里会调用一次obj的retian方法,引用计数会加一。

objec_autorelease

id
objc_autorelease(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->autorelease();
}
复制代码

与retian几乎相同,调用的是obj的autorelease方法。

objc_removeAssociatedObjects

void _object_remove_assocations(id object) {
    // 声明一个vector容器,key类型是ObjcAssociation,value类型是ObjcAllocator<ObjcAssociation>,这是一个构造器结构体,在runtime时内部使用。
    // vector在C++中是一个动态长度的顺序容器。
    vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
    {
        //创建一个AssociationsManager并加锁。
        AssociationsManager manager;
        // 获取AssociationsHashMap。
        AssociationsHashMap &associations(manager.associations());
        // 如果AssociationsHashMap中没有没有元素,则结束流程。
        if (associations.size() == 0) return;
        // 通过object构造disguised_object。
        disguised_ptr_t disguised_object = DISGUISE(object);
        // 在AssociationsHashMap中查找disguised_object对应的ObjectAssociationMap。
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        // 如果ObjectAssociationMap存在。
        if (i != associations.end()) {
            // 获得ObjectAssociationMap。
            ObjectAssociationMap *refs = i->second;
            // 循环迭代ObjectAssociationMap
            for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                //将所有的ObjectAssociation都存入vector中。
                elements.push_back(j->second);
            }
			// 释放ObjectAssociationMap的内存空间。
            delete refs;
            // 在AssociationsHashMap中删除这个ObjectAssociationMap元素。
            associations.erase(i);
        }
    }
	// 循环释放vector中保存的每一个ObjectAssociation 
    for_each(elements.begin(), elements.end(), ReleaseValue());
}
复制代码

生命周期


//文件:runtime.h
/** 
 * Destroys an instance of a class without freeing memory and removes any
 * associated references this instance might have had.
 * 摧毁一个实例对象,不会释放和移除任何此实例对象的关联对象。
 * 
 * @param obj The class instance to destroy.
 * 
 * @return \e obj. Does nothing if \e obj is nil.
 * 
 * @note CF and other clients do call this under GC.
 */
OBJC_EXPORT void * _Nullable objc_destructInstance(id _Nullable obj) 
    OBJC_AVAILABLE(10.6, 3.0, 9.0, 1.0, 2.0)
    OBJC_ARC_UNAVAILABLE;



/***********************************************************************
* object_dispose
* fixme
* Locking: none

文件:objc-runtime-new.m
**********************************************************************/
id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}




// ===============文件:objc-private.h ========================
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);
    }
}

// ===============文件:NSObject.mm ========================


void
_objc_rootDealloc(id obj)
{
    assert(obj);

    obj->rootDealloc();
}

// Replaced by NSZombies
- (void)dealloc {
    _objc_rootDealloc(self);
}

复制代码

从上面的代码可以看出,当NSObject执行dealloc的时候,并不会清理掉关联对象,所以关联对象是需要我们手动维护的,用完及时清理,避免出现内存泄露。

总结

  • 可以看出Runtime对于关联对象的管理都是线程安全的,增删查改都是加锁的。
  • 在App运行期间,由AssociationsHashMap来管理所有被添加到对象中的关联对象。
  • NSObject在dealloc的时候不会清理关联对象,需要手动维护关联对象的内存管理。
关注下面的标签,发现更多相似文章
评论