美文网首页
底层原理:关联对象

底层原理:关联对象

作者: 飘摇的水草 | 来源:发表于2022-03-12 16:54 被阅读0次

    在分类中声明属性的时候,只会自动生成方法的声明,并不会生成方法的实现和成员变量,举例说明:

    #import "Person.h"
    
    @interface Person (Test)
    
    @property (assign, nonatomic) int weight;
    
    - (void)setWeight:(int)weight;
    - (int)weight;
    
    @end
    

    即通过上述代码声明 weight 属性时,其只会声明 setWeight: 方法和 weight 方法,却并不会自动生成这两个方法的实现和 _weight 变量,如果直接在头文件里声明成员变量则编译不通过。

    手动实现属性效果

    如果想自己实现类似属性的用法,可以用下面的方式来实现效果:

    #import "Person.h"
    
    @interface Person (Test)
    
    @property (assign, nonatomic) int weight;
    
    @end
    
    @implementation Person (Test)
    
    NSMutableDictionary *weights_;
    + (void)load
    {
       weights_ = [NSMutableDictionary dictionary];
    }
    
    - (void)setWeight:(int)weight
    {
       NSString *key = [NSString stringWithFormat:@"%p", self];
       weights_[key] = @(weight);
    }
    
    - (int)weight
    {
       NSString *key = [NSString stringWithFormat:@"%p", self];
       return [weights_[key] intValue];
    }
    @end
    

    这种方案的缺点是: weights_ 这个字典存在内存泄漏的问题。

    关联对象

    分类不能直接添加成员变量,但可以通过关联的方式间接添加成员变量。将传入的属性值和对象关联起来,所以叫关联对象,我们先来看下关联对象的实现方式

    #import "Person.h"
    
    @interface Person (Test)
    
    @property (copy, nonatomic) NSString *name;
    @property (assign, nonatomic) int weight;
    
    @end
    
    #import "Person+Test.h"
    #import <objc/runtime.h>
    
    @implementation Person (Test)
    
    static const void *nameKey = &nameKey;
    static const void *weightKey = &weightKey;
    
    - (void)setName:(NSString *)name
    {
       objc_setAssociatedObject(self, nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    - (NSString *)name
    {
       return objc_getAssociatedObject(self, nameKey);
    }
    
    - (void)setWeight:(int)weight
    {
       objc_setAssociatedObject(self, weightKey, @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    - (int)weight
    {
          return [objc_getAssociatedObject(self, weightKey) intValue];
    }
    @end
    

    此时我们即可像正常访问其他属性一样对 nameweight 属性进行取值和赋值

    添加关联对象

    objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
    
    /*  参数解读
    * object : 给哪个对象添加属性。这里要给自己添加属性,用self。
    * key : 属性名,根据key获取关联对象的属性的值。传入一个指针即可。在objc_getAssociatedObject中通过次key获得属性的值并返回。
    * value : 关联的值,也就是通过set方法传入的值给属性。
    * policy : 策略,属性以什么形式保存。
    */
    

    其中参数policy对应的枚举值如下:

    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_getAssociatedObject(id object, const void *key);
    /*  参数解读
    * object : 获取哪个对象里面的关联的属性。
    * key : 属性,与objc_setAssociatedObject中的key相对应,即通过key值取出value。
    

    移除所有关联对象

    - (void)removeAssociatedObjects
    {
       // 移除所有关联对象
       objc_removeAssociatedObjects(self);
    }
    

    通过关联对象添加的成员变量在内存中不是存储在实例对象中,也不是存储在类对象中,原理后面有解读

    key的设置方式

    其中设置 key 可以有多种方式,一种是上面所示的那样通过如下方式:

    static const void *nameKey = &nameKey;
    static const void *weightKey = &weightKey;
    

    这种方式的缺点一是比较繁琐,二是如果不加 static 关键字修饰,则可以在其他文件中通过 extern const void *nameKey 这种方式来访问 nameKeystatic 在这里的作用是表示仅限当前文件

    第二种,可以通过下面这种

    static const char *nameKey;
    
    - (void)setName:(NSString *)name
    {
       objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    - (NSString *)name
    {
       return objc_getAssociatedObject(self, &nameKey);
    }
    

    这种方案相比第一种,变量只占用一个字节的存储空间

    第三种方式:

    - (void)setName:(NSString *)name
    {
       objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    - (NSString *)name
    {
       //_cmd == @selector(name)
       return objc_getAssociatedObject(self, @selector(name));
    }
    
    关联对象的原理

    实现关联对象技术的核心:

    • AssociationsManager
    • AssociationsHashMap
    • ObjectAssociationMap
    • ObjcAssociation

    其中Map同平时使用的字典相似,我们用下面这张图来演示这四者之间的关系:

    我们分别进入这4个对象内部查看它们之间的关系:

    AssociationsManager

    通过 AssociationsManager 内部源码发现,其内部有一个 AssociationsHashMap 对象:

    AssociationsHashMap

    我们继续查看 AssociationsHashMap 内部的源码,发现 AssociationsHashMap 继承自 unordered_map :

    我们看到里面还包括黄框标注的 ObjectAssociationMap ,我们先继续查看unordered_map 内部源码:

    unordered_map 源码中我们可以看出 _Key_Tp 也就是前两个参数对应着map中的 KeyValue ,那么对照上面 AssociationsHashMap 内源码发现_Key 中传入的是 disguised_ptr_t_Tp 传入的值则为 ObjectAssociationMap*( AssociationsHashMap 图中用红线标注)。

    同样我们来到黄框标注 ObjectAssociationMap 中,发现 ObjectAssociationMap 中同样也是将 ObjcAssociation 以参数形式传入,并以 keyValue 的方式存储着 ObjcAssociation ( AssociationsHashMap 图中用黄线标注)。

    我们继续来到 ObjcAssociation 源码中查看:

    我们发现 ObjcAssociation 存储着 _policy_value ,而这两个值我们发现正是我们调用添加关联对象 objc_setAssociatedObject 函数传入的值。也就是说我们在调用 objc_setAssociatedObject 函数中传入的 valuepolicy 这两个值最终是存储在 ObjcAssociation 中的。

    现在我们已经对 AssociationsManagerAssociationsHashMapObjectAssociationMapObjcAssociation 四个对象之间的关系有了简单的认识,那么接下来我们回到 objc_setAssociatedObject 源码,具体查看一下objc_setAssociatedObject 函数中传入的四个参数究竟做了哪些操作:

    首先根据传入的 value 经过 acquireValue 函数处理获取 new_valueacquireValue 函数内部其实是通过对策略的判断返回不同的值:

    创建 AssociationsManager 类型的 manager,拿到 manager 内部的AssociationsHashMap,即 Associations

    传入的 object 参数经过 DISGUISE 函数被转化为了 disguised_ptr_t 类型的disguised_object
    其实 DISGUISE 函数内部只是对 object 做了位运算:

    我们看到被处理成 new_valuevalue,和 policy 一起被存入了ObjcAssociation 中,而 ObjcAssociation 对应我们传入的 key 被存入了ObjectAssociationMap 中,disguised_objectObjectAssociationMap 则以key-value 的形式对应存储在 Associations 中也就是 AssociationsHashMap 中。

    总结

    • 一个实例对象就对应一个 ObjectAssociationMap ,而 ObjectAssociationMap 里面存储着多个此实例对象的关联对象的 key 以及 ObjcAssociationObjcAssociation 中存储着关联对象的value和policy。
    • 关联对象并不是存储在被关联对象本身内存中,而是存储在全局的统一的一个AssociationsManager
    • 如果设置关联对象为nil,就相当于是移除关联对象。
    • 这种毕竟不是属性,没有弱引用

    源码的解读在objc4的objc-references.mm

    相关文章

      网友评论

          本文标题:底层原理:关联对象

          本文链接:https://www.haomeiwen.com/subject/ejmkdrtx.html