美文网首页编写高质量代码的52个有效方法
52个有效方法(10) - 在既有类中使用关联对象存放自定义数据

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

作者: SkyMing一C | 来源:发表于2018-08-29 17:40 被阅读7次

    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 <objc/runtime.h>
    // 使用对象关联需引入#import <objc/runtime.h>头文件
    @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
    
    #import "NSObject + EOC_CX .h"
    #import <objc/runtime.h>
    // 使用对象关联需引入#import <objc/runtime.h>头文件
    @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 <objc/runtime.h>头文件
        // 对象关联 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 <objc/runtime.h>头文件
        //把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) - 在既有类中使用关联对象存放自定义数据

        本文链接:https://www.haomeiwen.com/subject/tkxlbftx.html