在开发过程中,如果经常会遇到想要给现有的类(闭源类)添加自己的属性标志的需求,如果使用继承会显得麻烦和臃肿,但是category又不支持直接添加属性,这时候我们就可以利用运行时的特性来解决这一问题。

2.1 实现原理

在运行时中,存在一个可以为对象动态绑定属性的方法:

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy); Parameters object The source object for the association. key The key for the association. value The value to associate with the key key for object. Pass nil to clear an existing association. policy The policy for the association. For possible values, see objc_AssociationPolicy.

使用该方法可以动态地使用给定的标志将属性值绑定到指定的对象:

  • object:需要关联属性的原对象(也就是需要把属性关联到到那个对象上);
  • key:关联的标志(用于获取属性的标志键);
  • value:需要关联的属性值(需要关联到对象的属性值);
  • policy:关联的属性语义(以何种方式引用).

policy是一组枚举值:

  • OBJC_ASSOCIATION_ASSIGN:标志对于关联对象的一个弱引用,相当于语义属性中的@property(assign,atomic);
  • OBJC_ASSOCIATION_RETAIN:标志对于关联对象的一个强引用,相当于语义属性中的@property(strong,atomic);
  • OBJC_ASSOCIATION_RETAIN_NONATOMIC:标志对于关联对象的一个强引用,相当于语义属性中的@property(strong,nonatomic);
  • OBJC_ASSOCIATION_COPY:标志对于关联对象的一个深拷贝,相当于语义属性中的@property(copy,atomic);
  • OBJC_ASSOCIATION_COPY_NONATOMIC:标志对于关联对象的一个深拷贝,相当于语义属性中的@property(copy,nonatomic).

2.2 如何使用

2.2.1 为一个UIButton对象绑定一个UIButtonInfo类型的对象,来传递参数

@interface UIButtonInfo : NSObject
@property (strong, nonatomic) NSDictionary *config;
@end
@implementation UIButtonInfo
@end


@interface UIButton (Identifier)
@property (strong, nonatomic) UIButtonInfo *info;
@end

@implementation UIButton (Identifier)
- (void)setInfo:(UIButtonInfo *)info {
    objc_setAssociatedObject(self, @selector(info), info, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
}
- (UIButtonInfo *)info {
    return objc_getAssociatedObject(self, @selector(info));
}
@end



//1. 在使用时,我们可以这样:
    button.info = ({
        UIButtonInfo *info = [[UIButtonInfo alloc] init];
        info.config = @{
                        @"from" : @"Appdelegate",
                        @"params" : @{
                                @"key" : @"value"
                                
                                }
                        };
        info;
    });
    [button addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];


//2. 然后在click事件中获取到参数:
- (void)click:(UIButton *)sender {
     NSLog(@"button.info == %@", sender.info.config);
}

2.2.2 我们将policy修改为OBJC_ASSOCIATION_COPY_NONATOMIC,运行一下看看会发生什么?

java实体类如何动态的增加字段_动态增加属性和方法

是的,报错了!!!!!

这是因为我们自定义的UIButtonInfo类并没有使NSCopiny协议,导致copy操作时出现错误。

@interface UIButtonInfo : NSObject<NSCopying>
@property (strong, nonatomic) NSDictionary *config;
@end
@implementation UIButtonInfo
- (id)copyWithZone:(NSZone *)zone {
    UIButtonInfo *info = [[UIButtonInfo alloc] init];
    info.config = self.config;
    return info;
}
@end

2.2.3 使用static char 作为key

@interface UIButton (Identifier)
@property (strong, nonatomic) UIButtonInfo *info;

@end

@implementation UIButton (Identifier)

static const char UIButtonInfo_Key;

- (void)setInfo:(UIButtonInfo *)info {
    objc_setAssociatedObject(self, &UIButtonInfo_Key, info, OBJC_ASSOCIATION_COPY_NONATOMIC);
    
}
- (UIButtonInfo *)info {
    return objc_getAssociatedObject(self, &UIButtonInfo_Key);
}
@end

在AFN中,多数动态属性使用@selector作为key,在SDWebImage中,作者更喜欢使用静态变量来作为key,所以究竟要用哪一种,取决于个人习惯,没有太大的差异.

2.2.4 nonatomic 与atomic

这两个语义属性略微有点诡异,相信大多数人使用nonatomic居多.而事实上,OC默认的语意属性确实atomic,也就是说apple好像更加倾向于原子属性,那么为什么我们却更加亲赖nonatomic呢?

atomic和nonatomic的主要区别在于编译器生成的getter和setter方法是否是原子操作.atomic支持多线程安全访问,也就是在访问属性的过程中,系统会自动创建锁,加锁,释放锁等操作来确保多线程属性访问的数据安全;而nonatomic禁止多线程变量保护,提升了访问的效率.所以虽然atomic保证了多线程的属性访问安全,却增加了系统的开销,尤其是在属性比较多或者频繁访问属性的过程中,使得访问效率低下,而对于大多数的项目来讲,不需要支持多线程的数据访问,或者只有少数方法需要支持多线程的数据访问(可以对需要支持的方法手动加锁),所以在日常项目中我们会更多地使用到nonatomic.当然,如果你的项目需要支持绝大多数方法的多线程访问的话,atomic还是很有帮助的.