10. 在既有类中使用关联对象存放自定义数据
“关联对象”(Associated Object) 是指动态创建一个指针从一个对象指向另外一个对象,并且遵循相应的“内存管理语义”,相当于动态添加一个属性。
关联的类型
存储对象值的时候,可以指明“存储策略”(storage policy),用以维护相应的“内存管理语义”。
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /** assign < Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**nonatomic, retain< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**nonatomic, copy< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**retain< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**copy< Specifies that the associated object is copied.
* The association is made atomically. */
};
关联的相关api
设置关联对象
void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)
/*
此方法以给定的键和策略为某对象设置关联对象值(将值value与对象object关联起来)
参数key:const void * 类型,将来可以通过key取出这个存储的值
参数policy:存储策略(assign、copy、retain)
*/
获取关联对象
id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
/*此方法根据给定的键从某对象中获取相应的关联对象值 */
移除关联对象
void objc_removeAssociatedObjects(id _Nonnull object)
/*此方法移除指定对象的全部关联对象 */
以静态全局变量作为key
设置关联对象的key和NSDictionary中的key不一样。其在术语上属于“不透明的指针”。
NSDictionary中,两个key如果isEqual方法返回YES,则认为两个key相同。
而这里要完全相同。鉴于此,在设置关联对象值时,通常使用静态全局变量做键。
关联对象用法举例
1. 给分类添加属性
分类的作用:在不改变原来类内容的基础上,可以为类增加一些方法。使用注意:
分类只能增加方法,不能增加成员变量(使用objc/runtime中的objc_setAssociatedObject(关联)可以给分类添加属性)。
分类方法实现中可以访问原来类中声明的成员变量。
分类可以重新实现原来类中的方法,但是会覆盖掉原来的方法,会导致原来的方法没法再使用。
方法调用的优先级:分类(最后参与编译的分类优先) --> 原来类 --> 父类。
@interface NSObject (EOC_CX)
/**
* 为每一个对象添加一个name属性
*/
@property (nonatomic,copy) NSString *name;
/**
* 为每个对象添加一个View属性
*/
@property (nonatomic,strong) UIView *booksView;
/**
* 为每个对象添加一个是否被选中属性
*/
@property(nonatomic, assign) BOOL isSelected;
@end
- 第一种写法
#import "NSObject + EOC_CX .h"
#import
// 使用对象关联需引入#import 头文件
@implementation NSObject (EOC_CX)
// 用一个字节来存储key值,设置为静态私有变量,避免外界修改
static void *nameKey;
- (void)setName:(NSString *)name
{
// 将某个值与某个对象关联起来,将某个值存储到某个对象中
objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, &nameKey);
}
static void *booksViewKey;
- (void)setBooksView:(UIView *) booksView
{
objc_setAssociatedObject(self, &booksViewKey, booksView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIView *) booksView
{
return objc_getAssociatedObject(self, &booksViewKey);
}
//将bool类型转变成NSNumber类型来进行添加属性 这样储存策略为OBJC_ASSOCIATION_COPY_NONATOMIC
static void *isSelectedKey;
- (void)setIsSelected:(BOOL)isSelected {
objc_setAssociatedObject(self, &isSelectedKey, @(isSelected), OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (BOOL)isSelected {
return [((NSNumber *) objc_getAssociatedObject(self, &isSelectedKey)) boolValue];
}
@end
- 第二种写法(该用法取自于: forkingdog / UITableView-FDTemplateLayoutCell 中的用法)
#import "NSObject + EOC_CX .h"
#import
// 使用对象关联需引入#import 头文件
@implementation NSObject (EOC_CX)
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
return objc_getAssociatedObject(self, _cmd);
//_cmd 代替了 &nameKey 或者 @selector(name).
}
- (void)setBooksView:(UIView *)booksView
{
objc_setAssociatedObject(self, @selector(booksView), booksView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIView *) booksView
{
return objc_getAssociatedObject(self, _cmd);
//_cmd 代替了 &booksKey 或者 @selector(booksView).
}
//将bool类型转变成NSNumber类型来进行添加属性 这样储存策略为OBJC_ASSOCIATION_COPY_NONATOMIC
- (void)setIsSelected:(BOOL)isSelected {
objc_setAssociatedObject(self, @selector(isSelected), @(isSelected), OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (BOOL)isSelected {
return [objc_getAssociatedObject(self, _cmd) boolValue];
//_cmd 代替了 &isSelectedKey 或者 @selector(isSelected).
}
@end
2. 在既有类中使用关联对象存放自定义数据
- (void)viewDidLoad {
[super viewDidLoad];
[self showAlertView];
}
- (void)showAlertView
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"UIAlertView" message:@"what do you do" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"sure", nil];
// 将逻辑定义到代码块里面
void(^block)(NSInteger) = ^(NSInteger buttonIndex) {
if (buttonIndex == 0) {
NSLog(@"%ld",buttonIndex);
} else {
NSLog(@"%ld",buttonIndex);
}
};
// 使用对象关联需引入#import 头文件
// 对象关联 block 用OBJC_ASSOCIATION_COPY_NONATOMIC
objc_setAssociatedObject(alert, @selector(alertView:clickedButtonAtIndex:), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
[alert show];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
void(^block)(NSInteger) = objc_getAssociatedObject(alertView, _cmd);
block(buttonIndex);
}
3. 传递数据
- (void)viewDidLoad {
[super viewDidLoad];
// static const char associatedButtonkey
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
[btn setTitle:@"点我" forState:UIControlStateNormal];
[self.view addSubview:btn];
[btn setFrame:CGRectMake(50, 50, 50, 50)];
btn.backgroundColor = [UIColor redColor];
[btn addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];
}
-(void)click:(UIButton *)sender
{
NSString *message = @"你是谁";
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"提示" message:@"我要传值·" delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil];
alert.delegate = self;
[alert show];
// 使用对象关联需引入#import 头文件
//把alert和message字符串关联起来,作为alertview的一部分,关键词就是msgstr,之后可以使用objc_getAssociatedObject从alertview中获取到所关联的对象,便可以访问message或者btn了
//即实现了关联传值
objc_setAssociatedObject(alert, @"msgstr", message,OBJC_ASSOCIATION_ASSIGN);
objc_setAssociatedObject(alert, @"btn property",sender,OBJC_ASSOCIATION_ASSIGN);
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
//通过 objc_getAssociatedObject获取关联对象
NSString *messageString =objc_getAssociatedObject(alertView, @"msgstr");
UIButton *sender = objc_getAssociatedObject(alertView, @"btn property");
NSLog(@"%ld",buttonIndex);
NSLog(@"%@",messageString);
NSLog(@"%@",[[sender titleLabel] text]);
//使用函数objc_removeAssociatedObjects可以断开所有关联。通常情况下不建议使用这个函数,因为他会断开所有关联。只有在需要把对象恢复到“原始状态”的时候才会使用这个函数。
}
要点
可以通过“关联对象”机制来把两个对象连起来。
定义关联对象时可指定内存管理语义,用以模仿定义属性时采用的拥有关系与非拥有关系。
只有在其他做法不可行时才应选用关联对象,因为这种做法通常会引入难于查找的bug。