参考链接
Objective-C Runtime 运行时之二:成员变量与属性
OC 中runtime的理解 以及 使用
什么是关联对象
小马在面试的时候,遇到这样一个问题:“如何給NSArray添加一个属性(不能使用继承)”,小马立马蒙逼了,不能用继承,难道用分类?但是分类貌似只能添加方法不能添加属性啊,小马百思不得其解,直到后来接触到了runtime才恍然大悟。
什么是关联对象
关联对象是指某个OC对象通过一个唯一的key连接到一个类的实例上。
举个例子:xiaoming是Person类的一个实例,他的dog(一个OC对象)通过一根绳子(key)被他牵着散步,这可以说xiaoming和dog是关联起来的,当然xiaoming可以牵着多个dog。
怎样关联对象
//关联对象
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)
id object:被关联的对象(如xiaoming)
const void *key:关联的key,要求唯一
id value:关联的对象(如dog)
objc_AssociationPolicy policy:内存管理的策略
我们可以把关联对象想象成一个Objective-C对象(如字典),这个对象通过给定的key连接到类的一个实例上。不过由于使用的是C接口,所以key是一个void指针(const void *)。我们还需要指定一个内存管理策略,以告诉Runtime如何管理这个对象的内存。这个内存管理的策略可以由以下值指定:
OBJC_ASSOCIATION_ASSIGN
OBJC_ASSOCIATION_RETAIN_NONATOMIC
OBJC_ASSOCIATION_COPY_NONATOMIC
OBJC_ASSOCIATION_RETAIN
OBJC_ASSOCIATION_COPY
当宿主对象被释放时,会根据指定的内存管理策略来处理关联对象。如果指定的策略是assign,则宿主释放时,关联对象不会被释放;而如果指定的是retain或者是copy,则宿主释放时,关联对象会被释放。我们甚至可以选择是否是自动retain/copy。当我们需要在多个线程中处理访问关联对象的多线程代码时,这就非常有用了。
我们将一个对象连接到其它对象所需要做的就是下面两行代码:
static char myKey;
objc_setAssociatedObject(self, &myKey, anObject, OBJC_ASSOCIATION_RETAIN);
在这种情况下,self对象将获取一个新的关联的对象anObject,且内存管理策略是自动retain关联对象,当self对象释放时,会自动release关联对象。另外,如果我们使用同一个key来关联另外一个对象时,也会自动释放之前关联的对象,这种情况下,先前的关联对象会被妥善地处理掉,并且新的对象会使用它的内存。
id anObject = objc_getAssociatedObject(self, &myKey);
我们可以使用objc_removeAssociatedObjects函数来移除一个关联对象,或者使用objc_setAssociatedObject函数将key指定的关联对象设置为nil。值得注意的是,我们不需要主动调用removeAssociated来接触关联的对象,如果需要解除指定的对象,可以使用setAssociatedObject置nil来实现。因为调用removeAssociated的时候你不知道这个person到底牵了多少只dog。
关联对象的应用
给分类添加属性
@interface Dog (AddProperty)
@property (nonatomic,copy)NSString *master;
@end
static char *associatedObjectKey;
@implementation Dog (AddProperty)
- (NSString *)master {
// return objc_getAssociatedObject(self, _cmd);
return objc_getAssociatedObject(self, associatedObjectKey);;
}
- (void)setMaster:(NSString *)master {
//设置关联对象
// objc_setAssociatedObject(self, @selector(master), master, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(self, associatedObjectKey, master, OBJC_ASSOCIATION_RETAIN_NONATOMIC); //获取关联对象
}
注意:我们也可以把方法的地址作为key,_cmd代表当前调用方法的地址。
我们也可以把一个对象绑定到任何对象上,假定我们想要动态地将一个Tap手势操作连接到任何UIView中,并且根据需要指定点击后的实际操作。这时候我们就可以将一个手势对象及操作的block对象关联到我们的UIView对象中。这项任务分两部分。首先,如果需要,我们要创建一个手势识别对象并将它及block做为关联对象。如下代码所示
- (void)setTapActionWithBlock:(void (^)(void))block
{
UITapGestureRecognizer *gesture = objc_getAssociatedObject(self, &kDTActionHandlerTapGestureKey);
if (!gesture)
{
gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(__handleActionForTapGesture:)];
[self addGestureRecognizer:gesture];
objc_setAssociatedObject(self, &kDTActionHandlerTapGestureKey, gesture, OBJC_ASSOCIATION_RETAIN);
}
objc_setAssociatedObject(self, &kDTActionHandlerTapBlockKey, block, OBJC_ASSOCIATION_COPY);
}
这段代码检测了手势识别的关联对象。如果没有,则创建并建立关联关系。同时,将传入的块对象连接到指定的key上。注意block
对象的关联内存管理策略。
手势识别对象需要一个target
和action
,所以接下来我们定义处理方法:
- (void)__handleActionForTapGesture:(UITapGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateRecognized)
{
void(^action)(void) = objc_getAssociatedObject(self, &kDTActionHandlerTapBlockKey);
if (action)
{
action();
}
}
}
我们需要检测手势识别对象的状态,因为我们只需要在点击手势被识别出来时才执行操作。
从上面的例子我们可以看到,关联对象使用起来并不复杂。它让我们可以动态地增强类现有的功能。我们可以在实际编码中灵活地运用这一特性。