52个有效方法(10) - 在既有类中使用关联对象存放自定义数据

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可以断开所有关联。通常情况下不建议使用这个函数,因为他会断开所有关联。只有在需要把对象恢复到“原始状态”的时候才会使用这个函数。
}
要点
  1. 可以通过“关联对象”机制来把两个对象连起来。

  2. 定义关联对象时可指定内存管理语义,用以模仿定义属性时采用的拥有关系与非拥有关系。

  3. 只有在其他做法不可行时才应选用关联对象,因为这种做法通常会引入难于查找的bug。

你可能感兴趣的:(52个有效方法(10) - 在既有类中使用关联对象存放自定义数据)