坚持 成长 每日一篇
前言
在Runtime中有一个我们经常忽略的特性就是关联对象特性。
关联对象类似于成员变量,不同的是关联对象是在类创建实例后添加进去的。所以不能通过Runtime的获取成员变量的方法获取相关信息。
注意:对关联函数的操作只能通过下面的C函数进行操作。
// 设置关联对象
void objc_setAssociatedObject ( id object, const void *key, id value, objc_AssociationPolicy policy );
// 获取关联对象
id objc_getAssociatedObject ( id object, const void *key );
//移除关联对象
void objc_removeAssociatedObjects ( id object );
在对于系统的类如果我们想个类添加属性,由于我们不能给类别里面添加成员变量,只能通过创建该类的子类来实现。
Objective-C针对这一问题,提供了一个解决方案:即关联对象(Associated Object)。
我们可以把关联对象想象成一个Objective-C对象(如字典),这个对象通过给定的key连接到类的一个实例上。不过由于使用的是C接口,所以key是一个void指针(const void *)。我们还需要指定一个内存管理策略,以告诉Runtime如何管理这个对象的内存。这个内存管理的策略可以由以下值指定:
enum {
OBJC_ASSOCIATION_ASSIGN = 0, //弱引用,对象销毁不会造成关联对象的引用计数变化
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, //强引用,不支持多线程安全
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, //深拷贝,不支持多线程安全
OBJC_ASSOCIATION_RETAIN = 01401, //强引用支持线程安全
OBJC_ASSOCIATION_COPY = 01403 //深拷贝,支持线程安全
};
例子
创建一个UIView的分类,我们要给分类添加一个单击手势回调代码块。
UIView+Tap.h
#import <UIKit/UIKit.h>
@interface UIView (Tap)
- (void)setTapActionWithBlock:(void (^)(void))block;
@end
UIView+Tap.m
#import "UIView+Tap.h"
#import <objc/runtime.h>
#define keyForAction "action"
#define keyForRecognizer "Recognizer"
@implementation UIView (Tap)
- (void)setTapActionWithBlock:(void (^)(void))block
{
//如果这里不使用关联对象,你可能需要不停移除手势,然后再创建手势
UITapGestureRecognizer *gesture = objc_getAssociatedObject(self,keyForRecognizer);
if (!gesture)
{
gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(actionForTapGesture:)];
[self addGestureRecognizer:gesture];
objc_setAssociatedObject(self, keyForRecognizer, gesture, OBJC_ASSOCIATION_RETAIN);
}
//这里可以成功的添加一个关联对象给后面的手势回调函数使用,而不需要通过全局变量或成员变量。
objc_setAssociatedObject(self, keyForAction, block, OBJC_ASSOCIATION_COPY);
}
//实现回调函数
- (void)actionForTapGesture:(UITapGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateRecognized)
{
//我们只能通过objc_getAssociatedObject来取出关联对象
void(^action)(void) = objc_getAssociatedObject(self, keyForAction);
if (action)
{
action();
}
}
}
@end
在控制器里面测试
- (void)viewDidLoad {
[super viewDidLoad];
//这样实现了不需要创建分类,很好的拓展了已有的UIView类。
[self.view setTapActionWithBlock:^{
NSLog(@"is tap");
}];
}
运行程序我们就会看到当我们点击控制视图时候输出
2015-09-15 09:59:21.869 test[5410:381831] is tap
2015-09-15 09:59:23.005 test[5410:381831] is tap
2015-09-15 09:59:23.545 test[5410:381831] is tap
总结:关联对象是一个非常实用的Runtime特性。可以很好的帮我们解决开发中遇到很多如传值,扩展对象等问题。