美文网首页
runtime-关联对象

runtime-关联对象

作者: Arthur澪 | 来源:发表于2020-03-12 15:29 被阅读0次

    前言

    场景:需要用一个系统的类,但系统的类并不能满足需求。你需要额外添加一个属性。一般解决办法就是继承。但只增加一个属性就去继承一个类,太麻烦。这个时候,runtime的关联对象就发挥它的作用了。

    给一个类添加属性,如:@property (assign, nonatomic) int age;
    实际上内部做了3件事:生成成员变量、声明并实现getter、setter

    Category可以添加成员方法,却不能直接利用Category添加成员变量。因为生成成员变量_age,会生成getter、setter的声明,但没有生成getter、setter的实现。
    另外,分类中也不能直接添加成员变量,会报错Instance variables may not be placed in categories

    这是因为Category的结构体中,没有数组存放成员变量,只有属性,协议等。

    struct category_t {
        const char *name;
        classref_t cls;
        struct method_list_t *instanceMethods;
        struct method_list_t *classMethods;
        struct protocol_list_t *protocols;
        struct property_list_t *instanceProperties;
        // Fields below this point are not always present on disk.
        struct property_list_t *_classProperties;
    
        method_list_t *methodsForMeta(bool isMeta) {
            if (isMeta) return classMethods;
            else return instanceMethods;
        }
    
        property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    };
    

    在Category中添加属性

    有3种方案:

    1.使用全局变量

    如:

    #import "YZPerson + PD.h"
    
    @implementation YZPerson (PD)
    
    int _weight;   // 全局变量
    
    - (void)setWeight:(int)weight{
        _weight = weight;
    }
    - (int)weight{
        return _weight;
    }
    @end
    
    YZPerson *person = [[YZPerson alloc] init];
    person.weight = 103;
    NSLog(@"weight = %d",person.weight);
    

    效果看起来是可以的。但因为全局变量是共享的,如果多个实例访问/修改这个变量,会有数据安全问题。

    2.采用字典

    针对上面的缺点,使用字典来保证一对一的关系。以对象的地址值作为key来,weight的值作为value来存储和使用。就不会因为不同对象而干扰了。

    但是因为是全局变量,问题还是明显。存在内存泄露问题、线程安全问题,另外每增加一个属性,都要写好多代码。不利于维护。

    3.关联对象

    下面来介绍这个方法

    关联对象方案

    先介绍runtime提供的相关接口:

    • 设置关联对象

    利用的接口方法:

    objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    

    参数说明:
    id object : 给哪个对象添加属性,这里要给自己添加属性,用self。
    void * == id key : key值,根据key获取关联对象的属性的值,在objc_getAssociatedObject中通过次key获得属性的值并返回。
    id value: 关联的值,也就是set方法传入的值给属性去保存。
    objc_AssociationPolicy policy: 策略,属性以什么形式保存。

    // 策略 枚举值
    typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
        OBJC_ASSOCIATION_ASSIGN = 0,  //  对应于 assign
        OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 对应于strong, nonatomic
        OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  //   copy, nonatomic
        OBJC_ASSOCIATION_RETAIN = 01401,  //     strong, atomic
        OBJC_ASSOCIATION_COPY = 01403     //   copy, atomic
    };
    

    上面列表中,没有对应weak修饰的策略

    • 获取关联对象的属性

    利用的方法:

    objc_getAssociatedObject(id object, const void *key);
    

    参数说明:
    id object: 获取哪个对象里面的关联的属性。
    void * == id key : 什么属性,与objc_setAssociatedObject中的key相对应,即通过key值取出value。

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

    举例:

    在Category中添加一个属性name

    #import "YZPerson.h"
    
    @interface YZPerson (PD)
    @property (nonatomic,strong) NSString *name;  // 添加的属性
    @end
    
    #import "YZPerson+PD.h"
    #import <objc/runtime.h>
    
    @implementation YZPerson (PD)
    
    const void *YZNameKey = &YZNameKey;
    
    - (void)setName:(NSString *)name{
        objc_setAssociatedObject(self, YZNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    - (NSString *)name{
       return objc_getAssociatedObject(self, YZNameKey);
    }
    
    - (void)dealloc{
        objc_removeAssociatedObjects(self);
    }
    
    @end
    

    使用起来,跟正常的属性一样。

    相关文章

      网友评论

          本文标题:runtime-关联对象

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