• Runtime


    主要围绕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

     

  • 相关阅读:
    vue 购买弹出框 动画
    vue 和animate.css 的动画使用
    获得url地址?后的参数
    Java 实现随机数组元素升降序
    java for循环实现九九乘法表
    java 随机生成字符串验证码
    Mysql插入值时,避免重复插入
    Mysql的unique和primary key
    2020 3.6日电话面试(某外包公司)
    Intellij IDEA配置javaweb项目
  • 原文地址:https://www.cnblogs.com/MrTao/p/9045033.html
Copyright © 2020-2023  润新知