关联对象的实现原理

简介: 这篇文章是来详细解释AssociationedObject的实现原理,篇幅较长

前言

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实现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,这个上面提到过,用来存放关联对象的值与关联策略。

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

  • _Allocator(allocator_type)对应的是ObjcAllocator >,用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时,会释放持有的关联对象。
目录
相关文章
|
6月前
|
NoSQL API Redis
数据对象的底层实现方式你都了解吗?
上一小节我们提到的五种数据类型其实就是 Redis 的数据对象,我们先来看看数据对象的类型:Redis 的 key 都是 string 类型的,以上各类型说的其实都是 value 的类型,以下是对象的几个优点:
54 0
数据对象的底层实现方式你都了解吗?
|
设计模式 Java 开发者
Spring框架中JavaBean的生命周期及单例模式与多列模式
Spring框架中JavaBean的生命周期及单例模式与多列模式
130 0
|
Java 数据库连接 Spring
【Spring事务底层实现原理】
【Spring事务底层实现原理】
|
SQL
延迟加载的底层原理知道吗?
延迟加载的底层原理知道吗?
96 0
|
SQL 存储 Java
|
SQL PHP 数据库
PDOstatement对象是干什么的?底层原理是什么?
PDOstatement对象是干什么的?底层原理是什么?
224 0
为什么说对象是类的一个实例?底层原理是什么?
为什么说对象是类的一个实例?底层原理是什么?
252 0
关联对象
默认情况下,因为分类底层结构的限制,分类中不能添加成员变量。
99 0
关联对象
|
设计模式 缓存 安全
Java动态代理机制和实现原理详解
本篇文章从设计模式中的代理模式开始入手,分析说明Java中的动态代理,详细分析了Java动态代理的实现机制以及Java动态代理的使用的注意点。通过一个Java动态代理实例对动态代理机制进行详细的说明。最后分析了动态代理中的class文件,动态代理的实现源码以及getClass0方法。通过这篇文章,可以对Java动态代理有一个整体而详尽的认识。
4976 0
Java动态代理机制和实现原理详解
|
Java 数据库
内省机制(操作javaBean的信息)
内省机制(操作javaBean的信息)
114 0
内省机制(操作javaBean的信息)