缘由:一般说来如果想用一个系统的类,但是该类又不满足需求,首先肯定是继承自系统的类,重新构建一个类,但这种方法,如果我们需求的类之比原来的类多一个属性呢,继承似乎又显得比较麻烦,这种情况,使用Category就非常的方便了,但是Category又不能直接生成属性,怎么办呢,这个时候也许用runtime的关联对象就可以办到了。
先看一下面两个方法
- 设置关联对象
@param object 需要添加关联的对象
@param key 添加的唯一标识符
@param value 关联的对
@param policy 关联的策略,是个枚举
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
- 获得关联对象
@param object 添加过关联的对象
@param key 添加的唯一标识符
objc_getAssociatedObject(id object, const void *key)
下面以** UITapGestureRecognizer**为例进行举例
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
typedef void (^PQTapBlock)(UITapGestureRecognizer *);
@interface UITapGestureRecognizer (PQBlock)
@property (nonatomic, copy) PQTapBlock tapBlock;
- (instancetype)initWithTapBlock:(void (^)(UITapGestureRecognizer *tap))tapBlock;
- (void)handleTapGesture:(void (^)(UITapGestureRecognizer *tap))tapBlock;
@end
NS_ASSUME_NONNULL_END
#import "UITapGestureRecognizer+PQBlock.h"
#import <objc/runtime.h>
static const char *PQTapBlockKey = "PQTapBlockKey";
@implementation UITapGestureRecognizer (PQBlock)
//init
- (instancetype)initWithTapBlock:(void (^)(UITapGestureRecognizer *tap))tapBlock {
self = [super init];
if (self) {
[self addTarget:self action:@selector(tap:)];
self.tapBlock = [tapBlock copy];
}
return self;
}
// Handle
- (void)handleTapGesture:(void (^)(UITapGestureRecognizer *))tapBlock {
[self addTarget:self action:@selector(tap:)];
self.tapBlock = [tapBlock copy];
}
// SEL
- (void)tap:(UITapGestureRecognizer *)tap {
if (self.tapBlock) {
self.tapBlock(tap);
}
}
// Get
- (PQTapBlock)tapBlock {
return objc_getAssociatedObject(self, PQTapBlockKey);
}
// Set
- (void)setTapBlock:(PQTapBlock)tapBlock {
objc_setAssociatedObject(self, PQTapBlockKey, tapBlock, OBJC_ASSOCIATION_COPY);
}
具体可以尝试两种用法测试
init
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTapBlock:^(UITapGestureRecognizer *tap){
NSLog(@"点击事件");
}];
[self.view addGestureRecognizer:tap];
handle
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] init];
[tap handleTapGesture:^(UITapGestureRecognizer *tap){
NSLog(@"点击事件");
}];
[self.view addGestureRecognizer:tap];
另外我们也可能注意到此处还有一个方法没用到。
OBJC_EXPORT void objc_removeAssociatedObjects(id object)
但是我们不应该自己手动调用这个函数。此函数的主要目的是在“初试状态”时方便地返回一个对象。你不应该用这个函数来删除对象的属性,因为可能会导致其他客户对其添加的属性也被移除了。规范的方法是:调用objc_setAssociatedObject
方法并传入一个nil
值来清除一个关联。同时此处再看看下面这个枚举策略加深理解。
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, //指定一个关联对象的弱引用。
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, //指定一个关联对象的强引用,不能被原子化使用。
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, //指定一个关联对象的copy引用,不能被原子化使用
OBJC_ASSOCIATION_RETAIN = 01401, //指定一个关联对象的强引用,能被原子化使用。
OBJC_ASSOCIATION_COPY = 01403, //指定一个关联对象的copy引用,能被原子化使用。
};
同时注意,下面这三种效果是一样的
//利用静态变量地址唯一不变的特性
1、static void *strKey = &strKey;
2、static NSString *strKey = @"strKey";
3、static char strKey;
为了更好理解此处,推荐看下面这文章
Objective-C Associated Objects 的实现原理
以及iOS-运行时(关联详解实例) 这个例子加深理解。