Runtime - Associated Objects (关联对象) 的实现原理

主要围绕3个方面说明runtime-Associated Objects (关联对象) 

1. 使用场景

2.如何使用

3.底层实现

   3.1  实现原理

   3.2 关联对象被存储在什么地方,是不是存放在被关联对象本身的内存中?

   3.3 关联对象的五种关联策略有什么区别,有什么坑?

   3.3 关联对象的生命周期是怎样的,什么时候被释放,什么时候被移除? 

 

  • 本文所使用的源码为 objc4-723

1. 使用场景

 我们知道哈,在 Objective-C 中可以通过 Category 给一个现有类/系统类添加方法,但是Objective-C与Swift 不同的是不能添加实例变量, 然而我们可以通过runtime - Associated Objects(关联对象) 来弥补这一不足.

2. 如何使用

思路其实很简单, 都知道在 Objective-C里面类的属性其实在编译的时候, 编译器都会转换成setter/getter方法, 所以我们就直接声明/实现属性的setter/getter方法,  我们来看看代码怎么写吧.

2.1 相关函数

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy); // 为object对象关联以key为键value值, 传入 nil 则可以移除已有的关联对象
id objc_getAssociatedObject(id object, const void *key);  // 读取object对象key键对应的值
void objc_removeAssociatedObjects(id object); // 移除object关联所有对象

 

2.1.1 key 值

关于前两个函数中的 key 值是我们需要重点关注的一个点,这个 key 值必须保证是一个唯一常量。一般来说,有以下三种推荐的 key 值: 

1. 声明 static char kAssociatedObjectKey; // 使用 &kAssociatedObjectKey 作为 key 值;
2. 声明 static void *kAssociatedObjectKey = &kAssociatedObjectKey; // 使用 kAssociatedObjectKey 作为 key 值;
3. 用 selector; // 使用 getter/setter 方法的名称作为 key 值, 还可以省一个变量.

那种写法都可以, 看个人喜好

 

2.1.2 关联策略  policy

关联策略                                   等价属性                                                说明
OBJC_ASSOCIATION_ASSIGN                   @property (assign) or @property (unsafe_unretained)   弱引用关联对象
OBJC_ASSOCIATION_RETAIN_NONATOMIC         @property (strong, nonatomic)                         强引用关联对象,且为非原子操作
OBJC_ASSOCIATION_COPY_NONATOMIC           @property (copy, nonatomic)                           复制关联对象,且为非原子操作
OBJC_ASSOCIATION_RETAIN                   @property (strong, atomic)                            强引用关联对象,且为原子操作
OBJC_ASSOCIATION_COPY                     @property (copy, atomic)                              复制关联对象,且为原子操作

 

2.2 如何使用

下面我们对NSString 进行关联一个 tempValue 属性

声明 NSString+TTExtension.h 文件

#import <Foundation/Foundation.h>

@interface NSString (TTExtension)

- (NSString *)tempValue; // getter

- (void)setTempValue:(NSString *)tempValue; // setter

@end

 

实现 NSString+TTExtension.m 文件

#import "NSString+TTExtension.h"
#import <objc/runtime.h>

static char dm_associated_key_id;

@implementation NSString (TTExtension)

- (NSString *)tempValue {
    return objc_getAssociatedObject(self, &dm_associated_key_id);}

- (void)setTempValue:(NSString *)tempValue {
    objc_setAssociatedObject(self, &dm_associated_key_id, tempValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

 

3. 底层实现

3.1 实现原理

3.1.1 objc_setAssociatedObject 设置关联对象

我们可以在 objc-runtime.mm 文件中找到 objc_setAssociatedObject 函数, 我们会发现这个函数内部调用了 _object_set_associative_reference 函数

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

 

接着可以在 objc-references.mm 文件中找到 _object_set_associative_reference 函数

/**
 * 关联引用对象
 ** Objective-C: objc_setAssociatedObject 底层调用的是 objc_setAssociatedObject_non_gc(有的版本是直接调用object_set_associative_refrence)
 ** 底层: objc_setAssociatedObject_non_gc调用了_object_set_associative_reference
 * object : 关联对象
 * key    : 可以认为是一个属性名
 * value  : 值
 * policy : 关联策略
 */
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // retain the new value (if any) outside the lock.
    ObjcAssociation old_association(0, nil); // 创建一个关联
    id new_value = value ? acquireValue(value, policy) : nil; // acquireValue判断关联策略类型
    { 
        AssociationsManager manager; // 用 mutex_t(互斥锁)维护的AssociationsHashMap哈希表映射; 哈希表可以认为就是个dictionary
        AssociationsHashMap &associations(manager.associations()); // 获取到所有的关联对象
        disguised_ptr_t disguised_object = DISGUISE(object); // 把object对象包装成disguised_ptr_t类型
        if (new_value) {
            // break any existing association.
            // 遍历所有的被关联对象查找 object 对象对应的被关联对象
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            /// end()返回指向map末尾的迭代器, 并非最优一个元素. 如果想获取最后一个元素需要last=end()之后last--,last就是最后一个元素
            if (i != associations.end()) { // i != (末尾迭代器) 代表object之前有过关联
                // secondary table exists
                ObjectAssociationMap *refs = i->second; // 获取键值对i对应的value值, ObjectAssociationMap哈希表映射
                ObjectAssociationMap::iterator j = refs->find(key); // 遍历所有的关联策略查找 key 对应的关联策略
                if (j != refs->end()) { // 存在
                    old_association = j->second; // 缓存之前的值, 在下面会release这个值
                    j->second = ObjcAssociation(policy, new_value); // 把{(}最新值&关联策略}更新
                } else { // 不存在
                    (*refs)[key] = ObjcAssociation(policy, new_value); // 不存在直接根据key赋值{值&关联策略}
                }
            } else { // 不存在, 代表当前对象没有关联过对象, 所以直接创建ObjectAssociationMap储存{值&关联策略}
                // create the new association (first time).
                ObjectAssociationMap *refs = new ObjectAssociationMap; // 创建ObjectAssociationMap
                associations[disguised_object] = refs; // 储存 ObjectAssociationMap
                (*refs)[key] = ObjcAssociation(policy, new_value); // 创建关联策略
                // 储存关联策略
                object->setHasAssociatedObjects();  // data()->flags & RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS;
            }
        } else {
            // setting the association to nil breaks the association.
            // 代表赋值的为nil
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) { // 存在, 代表之前关联过
                ObjectAssociationMap *refs = i->second; // 找到 对象对应的ObjectAssociationMap
                ObjectAssociationMap::iterator j = refs->find(key); // 遍历查找是否有过赋值
                if (j != refs->end()) { // 如果有需要释放之前的对象
                    old_association = j->second; // 缓存之前的value对象, 后期释放
                    refs->erase(j); // 移除j元素
                }
            }
        }
    }
    // release the old value (outside of the lock).
    if (old_association.hasValue()) ReleaseValue()(old_association); // release之前的关联对象对应的value
}

备注:  win32不考虑

AssociationsManager

一个维护全部关联记录单例类.

// 一个单例类, 维护全部的关联内容
class AssociationsManager {
    // associative references: object pointer -> PtrPtrHashMap.
    static AssociationsHashMap *_map; // 静态的说明维护所有的对象的关联对象
public:
    AssociationsManager()   { AssociationsManagerLock.lock(); } // mutex_t 互斥锁: 加锁
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); } // mutex_t 互斥锁: 解锁
    
    AssociationsHashMap &associations() { // 是一个无序的哈希表, 维护了从对象地址到 ObjectAssociationMap 的映射; 我们发现了它还是一个懒加载的对象
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

AssociationsHashMap *AssociationsManager::_map = NULL; // 全局的说明所有的关联是一个单利对象管理

 

AssociationsHashMap

无序 hash 表,key 是被关联对象的地址. value 是 ObjectAssociationMap.

// 可以理解为所有被关联对象的全部关联记录;
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); }
};

 

ObjectAssociationMap

也是一个无序 hash 表,key 是关联对象设置的 key, value 是 ObjcAssociation

// 可以理解为一个被关联对象的所有关联对象
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); }
};

 

ObjcAssociation

1个关联对象的封装,里面维护了关联对象的 value 和 关联策略

//一个关联对象的封装
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; } // 关联对象是否有值
};
几个类的 大致关系
objc_setAssociatedObject(object, kAssociatedKey, value, policy);
AssociationsManager  ->  {
    lock // 锁保证数据的安全

    AssociationsHashMap -> { // 无序 hash 表
        &object = ObjectAssociationMap -> { // 无序 hash 表
            kAssociatedKey = ObjcAssociation - > { // 关联对象的封装
                value, 
                policy
            }
        }
    }

    unlock
}

 

3.1.2 objc_getAssociatedObject

我们可以在 objc-runtime.mm 文件中找到 objc_getAssociatedObject 函数, 我们会发现这个函数内部调用了_object_get_associative_reference 函数

id objc_getAssociatedObject(id object, const void *key) {
    return _object_get_associative_reference(object, (void *)key);
}

  

 接着在 objc-references.mm 文件中找到_object_get_associative_reference 函数

/** 不多解释. 看完_object_set_associative_reference看这个很简单
 * 关联引用对象
 ** Objective-C: objc_getAssociatedObject 最终会调用 _object_get_associative_reference来获取关联对象。
 ** 底层: _object_get_associative_reference
 * object : 被关联对象
 * key    : 可以认为是一个属性名
 */
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;
}

 

3.1.3 objc_removeAssociatedObjects

 在 objc-runtime.mm 文件中找到 objc_removeAssociatedObjects 函数, 我们会发现这个函数内部调用了_object_remove_assocations 函数

void objc_removeAssociatedObjects(id object) 
{
    if (object && object->hasAssociatedObjects()) { // 判断当前对象是否有关联对象
        _object_remove_assocations(object);
    }
}

 

 在 objc-references.mm 文件中找到_object_remove_assocations 函数 

/**
 * 移除关联对象
 ** Objective-C: objc_removeAssociatedObjects 底层调用的是 _object_remove_assocations
 ** 底层: _object_remove_assocations
 * object : 被关联对象
 */
void _object_remove_assocations(id object) {
    // 全部的关联策略
    vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        if (associations.size() == 0) return; // map中元素的个数为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; // 释放refs内存
            associations.erase(i); // 从associations 移除object
        }
    }
    // the calls to releaseValue() happen outside of the lock.
    // 遍历移除被关联对象中所有的 AssociationsHashMap 对象,并且对全部对象release
    for_each(elements.begin(), elements.end(), ReleaseValue());
}

 

3.2 关联对象被存储在什么地方,是不是存放在被关联对象本身的内存中?

其实看过AssociationsManager 类之后你就已经知道答案了, 关联的对象都存放在一个维护关联的单例类
 
// 一个单例类, 维护全部的关联内容
class AssociationsManager {
    // associative references: object pointer -> PtrPtrHashMap.
    static AssociationsHashMap *_map; // 静态的说明维护所有的对象的关联对象
public:
    AssociationsManager()   { AssociationsManagerLock.lock(); } // mutex_t 互斥锁: 加锁
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); } // mutex_t 互斥锁: 解锁
    
    AssociationsHashMap &associations() { // 是一个无序的哈希表, 维护了从对象地址到 ObjectAssociationMap 的映射; 我们发现了它还是一个懒加载的对象
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

AssociationsHashMap *AssociationsManager::_map = NULL; // 全局的说明所有的关联是一个单利对象管理

 

3.3 关联对象的五种关联策略为什么与底层的策略不一样?

这个是关联对象暴露的策略
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { 
    OBJC_ASSOCIATION_ASSIGN = 0,           // 0000 0000
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 0000 0001
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   // 0000 0011
                                           // 我们只看后8位, 从第0位开始算
    OBJC_ASSOCIATION_RETAIN = 01401,       // 0111 1001
    OBJC_ASSOCIATION_COPY = 01403          // 0111 1011
};
这个是底层的策略
// 有人很好奇怎么跟 objc_AssociationPolicy 不一样,其实objc_AssociationPolicy是根据NONATOMIC把runtime的OBJC_ASSOCIATION_SETTER 细分了, 仔细研究你就会发现objc_AssociationPolicy 与OBJC_ASSOCIATION_SETTER 有很大的联系的

enum {
    OBJC_ASSOCIATION_SETTER_ASSIGN      = 0,
    OBJC_ASSOCIATION_SETTER_RETAIN      = 1,
    OBJC_ASSOCIATION_SETTER_COPY        = 3,    // NOTE:  both bits are set, so we can simply test 1 bit in releaseValue below.
    OBJC_ASSOCIATION_GETTER_READ        = (0 << 8), 
    OBJC_ASSOCIATION_GETTER_RETAIN      = (1 << 8), 
    OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8)
};
 

我们可以看出下面代码在判断关联策略的时候都是 & 0xFF

/**
 * 判断关联值类型
 * value  : 值
 * policy : 关联策略
 */
static id acquireValue(id value, uintptr_t policy) {
    switch (policy & 0xFF) {
    case OBJC_ASSOCIATION_SETTER_RETAIN:
        return objc_retain(value); // retain value
    case OBJC_ASSOCIATION_SETTER_COPY:
        return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy); // copy object
    }
    return value;
}

而 objc_AssociationPolicy & 0xFF之后 再 & OBJC_ASSOCIATION_SETTER_*  会怎么样呢请看

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           // 0000 0000 & 1111 1111 = 0000 0000
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 0000 0001 & 1111 1111 = 0000 0001 & OBJC_ASSOCIATION_SETTER_RETAIN(0000 0001) = 0000 0001
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   // 0000 0011 & 1111 1111 = 0000 0011 & OBJC_ASSOCIATION_SETTER_COPY  (0000 0011) = 0000 0011
    // 我们只看后8位, 从第0位开始算
    OBJC_ASSOCIATION_RETAIN = 01401,       // 0111 1001 & 1111 1111 = 0111 1001 & OBJC_ASSOCIATION_SETTER_RETAIN(0000 0001) = 0000 0001
    OBJC_ASSOCIATION_COPY = 01403          // 0111 1011 & 1111 1111 = 0111 1011 & OBJC_ASSOCIATION_SETTER_COPY  (0000 0011) = 0000 0011
};

 & 0xFF 之后我们会发现 

OBJC_ASSOCIATION_RETAIN_NONATOMIC 和 OBJC_ASSOCIATION_RETAIN 其实最后 再 & OBJC_ASSOCIATION_RETAIN_NONATOMIC 居然就是 OBJC_ASSOCIATION_RETAIN_NONATOMIC, 所以虽然不一样但是其实是有内在联系的

而 OBJC_ASSOCIATION_COPY_NONATOMIC 和 OBJC_ASSOCIATION_COPY 其实也是一样的

OBJC_ASSOCIATION_GETTER_* 就不解释了其实也一样, 只不过我还没找到在什么位置用到, 如果你知道请留言

3.3 关联对象的生命周期是怎样的,什么时候被释放,什么时候被移除?

这个问题我们就得看看一个对象对销毁之后调用了上面, 都知道对象销毁会调用 - (void)dealloc {} 方法, 那么我们就看看runtime原码是怎么调用的

我们可以在 NSObject.mm  文件中找到 dealloc 函数, 可是dealloc 又调用了_objc_rootDealloc 函数

- (void)dealloc {
    _objc_rootDealloc(self);
}

_objc_rootDealloc 又调用了 对象内部的rootDealloc方法
// 释放内存, dealloc发起
void
_objc_rootDealloc(id obj)
{
    assert(obj);

    obj->rootDealloc(); // objc-object.h
}
 我们可以在 objc-object.h  文件中找到对象的实现方法 rootDealloc, 方法内部又调用object_dispose函数
// 释放内存
inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;
    object_dispose((id)this); // objc-runtime-new.m
}
在 objc-runtime-new.m 文件中找到object_dispose 函数,函数内部又调用 objc_destructInstance 函数
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); // 移除关联对象, 释放value
        obj->clearDeallocating();
    }

    return obj;
} 
1波三折, 大量的搜索之后我们终于找到了答案, 其实对象销毁前 最终会调用objc_destructInstance 函数, 其内部做了判断如果有关联对象会调用 _object_remove_assocations函数, 而_object_remove_assocations 函数内部把被关联对象的所有关联对象值对象 进行release 并且释放销毁当前被关联对象.

就写到这里, 如果那里写的不对请留言指正, 我们一起共同进步, 谢啦

相关参考技术blog

C++ :  https://www.cnblogs.com/fnlingnzb-learner/p/5833051.html

其他有关Associated Objects文章:

https://www.jianshu.com/p/48b1b04d0b87

 

 

posted @ 2018-05-16 11:23  To-J  阅读(710)  评论(0编辑  收藏  举报