在开发中有时候想给对象实例添加个变量来存储数据,但又无法直接声明,比如说既有类的分类。这个时候我们就可以通过 关联对象 在运行时给对象关联一个 对象 来存储数据。(注意:并不是真实的添加了一个实例变量)
关联对象 可以给某个对象关联其他对象并用key来区分其他对象。需要注意的是,存储对象的时候要指明 存储策略,用来维护对象的内存管理语义。存储策略是 objc_AssociationPolicy 枚举定义,以下是存储策略对应的 @property属性:
存储策略类型 | 对应的@property属性 |
---|---|
OBJC_ASSOCIATION_ASSIGN | weak |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | strong, nonatomic |
OBJC_ASSOCIATION_COPY_NONATOMIC | copy, nonatomic |
OBJC_ASSOCIATION_RETAIN | strong |
OBJC_ASSOCIATION_COPY | copy |
用下面的方法可以管理关联对象:
// 这个方法可以根据指定策略给对象关联对象值void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)// 这个方法可以获取对象关联对象值id objc_getAssociatedObject(id object, const void *key)// 这个方法可以删除指定对象的全部关联对象值void objc_removeAssociatedObjects(id object)复制代码
对于关联对象这个OC特性,我们可以把对象想象成一个 NSDictionary,关联对象需要一个 key( 类型是 opaque pointer,无类型的指针 ) 来区分,我们可以把要添加的变量名作为 key ,把变量的值作为关联的对象来存储到 ”对象“ 这个 NSDictionary 中。 所以,关联对象的
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)复制代码
方法类似于字典的 [dict setObject: forKey:]
方法。
在存储和获取关联对象时需要用一个相等的 key ,因为是给 Class 的实例对象关联对象,所以一般用静态变量来做 key 。
说的再多,不如上段代码!
比如说,我们给 NSString 实例加上个 NSDate 类型的 date 变量。什么?给字符串加个日期变量是要干袅?我要给字符串过个生日不行吗! 别闹,举个栗子嘛!(捂脸逃跑~~~)
首先,我们先给 NSString 新建个名为 RT 的 category。 在头文件中有个 NSDate 类型的 date 属性:
// NSString+RT.h// runtime#import//@interface NSString (RT)//@property (nonatomic, strong) NSDate *date;//@end复制代码
在分类中的属性只会生成 get 和 set 方法,并不会生成变量。 所以我们需要重写 get 和 set 方法,关联对象以变相实现添加变量,在现实文件中:
// NSString+RT.m// runtime#import#import "NSString+RT.h"//@implementation NSString (RT)//static void *runtime_date_key = "date";- (NSDate *)date{ return objc_getAssociatedObject(self, runtime_date_key);}//- (void)setDate:(NSDate *)date{ objc_setAssociatedObject(self, runtime_date_key, date, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}@end复制代码
需要注意的是,关联对象用到的 key 是个无类型的指针,一般来说是静态来修饰。 另外,给对象关联的只能是对象,如果是 int、 float 等类型需要 NSNumber 进行包装。 因为 date 是强引用和非原子属性,所以关联策略用 OBJC_ASSOCIATION_RETAIN_NONATOMIC
然后执行代码:
NSString *string = @"runtimeTestString";string.date = [NSDate date];NSLog(@"string.date = %@",string.date);复制代码
输出结果:
2016-04-12 21:27:31.099 runtime[2837:103727] string.date = 2016-04-12 13:27:31 +0000复制代码
注意:
- 定义关联对象时需要指定内存管理语义,用来模拟对象对变量的拥有关系
- 尽量避免使用关联对象,因为如果出现bug不易于问题排查
我的博客: