runtime-关联对象
前言
场景:需要用一个系统的类,但系统的类并不能满足需求。你需要额外添加一个属性。一般解决办法就是继承。但只增加一个属性就去继承一个类,太麻烦。这个时候,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
使用起来,跟正常的属性一样。