runtime-关联对象

2020-03-12  本文已影响0人  Arthur澪

前言

场景:需要用一个系统的类,但系统的类并不能满足需求。你需要额外添加一个属性。一般解决办法就是继承。但只增加一个属性就去继承一个类,太麻烦。这个时候,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

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

上一篇下一篇

猜你喜欢

热点阅读