73

在我接手的一个项目中,原作者选择使用它objc_setAssociatedObject(),我不是 100% 清楚它的作用或他们决定使用它的原因。

我决定查一下,不幸的是,文档对它的用途没有很好的描述。

objc_setAssociatedObject
使用给定键和关联策略为给定对象设置关联值。
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
参数
object
关联的源对象。
key
关联的密钥。
value
与对象的键键关联的值。通过 nil 清除现有关联。
policy
协会的政策。有关可能的值,请参阅“关联对象行为”。

那么这个函数到底是做什么的,应该在什么情况下使用呢?


阅读答案后编辑

那么下面的代码有什么意义呢?

Device *device = [self.list objectAtIndex:[indexPath row]];
DeviceViewController *next = [[DeviceViewController alloc] initWithController:self.controller
                                                                            device:device
                                                                               item:self.rootVC.selectedItem];  
    objc_setAssociatedObject(device, &kDeviceControllerKey, next, OBJC_ASSOCIATION_RETAIN);

如果设备已经是实例变量,那么将设​​备与视图控制器关联起来有什么意义?

4

4 回答 4

66

objc_setAssociatedObject为每个 Objective-C 对象添加一个键值存储。它允许您存储对象的其他状态,而不是反映在其实例变量中。

当您想在主实现之外存储属于对象的东西时,这真的很方便。主要用例之一是您无法添加实例变量的类别。在这里,您objc_setAssociatedObject可以将附加变量附加到self对象。

当使用正确的关联策略时,您的对象将在主对象被释放时被释放。

于 2011-05-06T09:44:13.777 回答
35

来自Objective-C 运行时参考的参考文档:

您使用 Objective-C 运行时函数objc_setAssociatedObject在一个对象和另一个对象之间建立关联。该函数有四个参数:源对象、键、值和关联策略常量。关键是一个空指针。

  • 每个关联的键必须是唯一的。典型的模式是使用静态变量。
  • 该策略指定关联对象是否被分配、
    保留或复制,以及
    关联是原子的还是
    非原子的。此模式
    类似于
    已声明属性的属性(请参阅“属性
    声明属性”)。您可以使用常量指定关系的策略(请参阅
    objc_AssociationPolicy 和
    关联对象行为)。

在数组和字符串之间建立关联

static char overviewKey;



NSArray *array =

    [[NSArray alloc] initWithObjects:@"One", @"Two", @"Three", nil];

// For the purposes of illustration, use initWithFormat: to ensure

// the string can be deallocated

NSString *overview =

    [[NSString alloc] initWithFormat:@"%@", @"First three numbers"];



objc_setAssociatedObject (

    array,

    &overviewKey,

    overview,

    OBJC_ASSOCIATION_RETAIN

);



[overview release];

// (1) overview valid

[array release];

// (2) overview invalid

在第 1 点,字符串概述仍然有效,因为 OBJC_ASSOCIATION_RETAIN 策略指定数组保留关联的对象。然而,当数组被释放时(在第 2 点),overview 被释放,因此在这种情况下也被释放。例如,如果您尝试记录overview 的值,则会生成运行时异常。

于 2011-05-06T09:33:49.973 回答
27

以下是对象关联的用例列表:

一:将实例变量添加到类别中。一般来说,建议不要使用这种技术,但这里是一个合法使用的例子。假设您想为无法修改的对象模拟附加的实例变量(我们正在讨论修改对象本身,即没有子类化)。假设在 UIImage 上设置标题。

// UIImage-Title.h:
@interface UIImage(Title)
@property(nonatomic, copy) NSString *title;
@end 

// UIImage-Title.m:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>

static char titleKey;

@implementation UIImage(Title)
- (NSString *)title
{
    return objc_getAssociatedObject(self, &titleKey);
}

- (void)setTitle:(NSString *)title
{
    objc_setAssociatedObject(self, &titleKey, title, OBJC_ASSOCIATION_COPY);
}
@end

此外,是一种使用与类别关联的对象的非常复杂(但很棒)的方法。它基本上允许您将块而不是选择器传递给UIControl.


二:结合KVO动态添加状态信息到其实例变量未覆盖的对象。

这个想法是您的对象仅在运行时(即动态)获得状态信息。所以这个想法是,虽然您可以将此状态信息存储在实例变量中,但您将此信息附加到在运行时实例化的对象中并将其与另一个对象动态关联的事实,您正在强调这样一个事实对象的动态状态。

一个很好的例子就是这个库,其中关联对象与KVO通知一起使用。这是代码的摘录(注意:运行此 KVO 通知不是使该库中的代码工作所必需的。而是作者为了方便而将其放在那里,基本上任何注册到此的对象都将通过 KVO 通知它发生了变化):

static char BOOLRevealing;

- (BOOL)isRevealing
{
    return [(NSNumber*)objc_getAssociatedObject(self, &BOOLRevealing) boolValue];
} 

- (void)_setRevealing:(BOOL)revealing
{
    [self willChangeValueForKey:@"isRevealing"];
    objc_setAssociatedObject(self, &BOOLRevealing, 
       [NSNumber numberWithBool:revealing], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [self didChangeValueForKey:@"isRevealing"];
}

奖励:看看这个由开创性 AFNetworking 库的作者 Mattt Thompson 对关联对象的讨论/解释

于 2013-05-01T05:48:43.087 回答
5

要回答您修改后的问题:

如果设备已经是实例变量,那么将设​​备与视图控制器关联起来有什么意义?

您可能想要这样做有几个原因。

  • Device 类没有控制器实例变量或属性,您无法更改它或对其进行子类化,例如您没有源代码。
  • 您想要两个与设备对象关联的控制器,并且您不能更改设备类或子类。

就个人而言,我认为很少需要使用低级别的 Objective-C 运行时函数。这对我来说看起来像是一种代码味道。

于 2011-05-06T10:31:43.410 回答