Associative references, 暂且称之为关联引用,从Mac OS X v10.6 开始可用, 模拟已经存在的类的额外对象实例。通过使用关联引用,你可以为一个对象增加存储空间而不必修改类的声明。当你无法访问类的源码或者由于二进制兼容问题你无法修改对象布局时,关联引用将会很有用。
关联是建立在键的基础上的。对于任何对象你都可以添加任意多的关联,每一个都使用不同的键。一个关联还可以确保关联的对象在源对象声明周期内都保持有效。
使用 Objective-C 的运行时函数 objc_setAssociatedObject
可以创建一个对象跟另外一个对象的关联。这个函数有4个参数:源对象, 键, 值以及一个关联原则常量。其中,键跟关联原则将深入探讨下:
键是一个 void
指针。每个关联的键必须都是唯一的。通常的形式是使用一个 static
变量。
原则定义了是否关联的对象是 assigned, retained, or copied以及是否关联是自动完成的。形式类似与属性的额外属性 (可以参考 “Property Declaration Attributes”). 你可以使用一个常量来为关系指定原则(可以参考 objc_AssociationPolicy
和 Associative Object Behaviors
).
6-1 展示了如何为一个数组跟字符串建立关联
6-1
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)处, overview
字符串仍然是有效可用的,因为 OBJC_ASSOCIATION_RETAIN
原则指定了数组将保持这个相关的对象。当数组也被释放后,(2)的位置,overview
就也被释放了,此时所有的都将释放。要是你还试图去使用 overview
,就会报运行时异常。
通过使用 Objective-C 的运行时函数 objc_getAssociatedObject
可用获得一个关联对象。让我们继续 6-1 中的例子,你可以通过下面的代码从 array 中获取到overview
NSString *associatedObject = |
(NSString *)objc_getAssociatedObject(array, &overviewKey); |
要断开关联,你可以调用 objc_setAssociatedObject
, 传一个 nil。
继续使用 6-1的例子,你可以使用下面的代码断开array跟overview
字符串的关联:
objc_setAssociatedObject(array, &overviewKey, nil, OBJC_ASSOCIATION_ASSIGN); |
关联的对象被设置为了 nil
, 至于原则项这里都不重要。
要断开一个对象的所有关联,你可以调用 objc_removeAssociatedObjects。
通常来说,不应该使用这个方法,因为它会对所有的客户断开所有关联。只有当你想把一个对象重置到初始化状态时再使用这个方法。
下面是本节完整的例子
#import |
#import |
|
int main (int argc, const char * argv[]) { |
|
@autoreleasepool { |
static char overviewKey; |
|
NSArray *array = [[NSArray alloc] |
initWithObjects:@ "One", @"Two", @"Three", nil]; |
// For the purposes of illustration, use initWithFormat: to ensure |
// we get a deallocatable string |
NSString *overview = [[NSString alloc] |
initWithFormat:@"%@", @"First three numbers"]; |
|
objc_setAssociatedObject ( |
array, |
&overviewKey, |
overview, |
OBJC_ASSOCIATION_RETAIN |
); |
[overview release]; |
|
NSString *associatedObject = |
(NSString *) objc_getAssociatedObject (array, &overviewKey); |
NSLog(@"associatedObject: %@", associatedObject); |
|
objc_setAssociatedObject ( |
array, |
&overviewKey, |
nil, |
OBJC_ASSOCIATION_ASSIGN |
); |
[array release]; |
|
} |
return 0; |
} |
Associative references, available starting in Mac OS X v10.6, simulate the addition of object instance variables to an existing class. Using associative references, you can add storage to an object without modifying the class declaration. This may be useful if you do not have access to the source code for the class, or if for binary-compatibility reasons you cannot alter the layout of the object.
Associations are based on a key. For any object you can add as many associations as you want, each using a different key. An association can also ensure that the associated object remains valid for at least the lifetime of the source object.
You use the Objective-C runtime function objc_setAssociatedObject
to make an association between one object and another. The function takes four parameters: the source object, a key, the value, and an association policy constant. Of these, the key and the association policy merit further discussion.
The key is a void
pointer. The key for each association must be unique. A typical pattern is to use a static
variable.
The policy specifies whether the associated object is assigned, retained, or copied, and whether the association is be made atomically or non-atomically. This pattern is similar to that of the attributes of a declared property (see “Property Declaration Attributes”). You specify the policy for the relationship using a constant (seeobjc_AssociationPolicy
and Associative Object Behaviors
).
Listing 6-1 shows how you can establish an association between an array and a string.
Listing 6-1 Establishing an association between an array and a string
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 |
At point 1, the string overview
is still valid because the OBJC_ASSOCIATION_RETAIN
policy specifies that the array retains the associated object. When the array is deallocated, however (at point 2), overview
is released and so in this case also deallocated. If you try to, for example, log the value of overview
, you generate a runtime exception.
You retrieve an associated object using the Objective-C runtime function objc_getAssociatedObject
. Continuing the example shown in Listing 6-1, you could retrieve the overview from the array using the following line of code:
NSString *associatedObject = |
(NSString *)objc_getAssociatedObject(array, &overviewKey); |
To break an association, you typically call objc_setAssociatedObject
, passing nil
as the value.
Continuing the example shown in Listing 6-1, you could break the association between the array and the stringoverview
using the following line of code:
objc_setAssociatedObject(array, &overviewKey, nil, OBJC_ASSOCIATION_ASSIGN); |
Given that the associated object is being set to nil
, the policy isn’t actually important.
To break all associations for an object, you can call objc_removeAssociatedObjects
. In general, however, you are discouraged from using this function because it breaks all associations for all clients. Use this function only if you need to restore an object to “pristine condition.”
The following program combines code from the preceding sections.
#import |
#import |
|
int main (int argc, const char * argv[]) { |
|
@autoreleasepool { |
static char overviewKey; |
|
NSArray *array = [[NSArray alloc] |
initWithObjects:@ "One", @"Two", @"Three", nil]; |
// For the purposes of illustration, use initWithFormat: to ensure |
// we get a deallocatable string |
NSString *overview = [[NSString alloc] |
initWithFormat:@"%@", @"First three numbers"]; |
|
objc_setAssociatedObject ( |
array, |
&overviewKey, |
overview, |
OBJC_ASSOCIATION_RETAIN |
); |
[overview release]; |
|
NSString *associatedObject = |
(NSString *) objc_getAssociatedObject (array, &overviewKey); |
NSLog(@"associatedObject: %@", associatedObject); |
|
objc_setAssociatedObject ( |
array, |
&overviewKey, |
nil, |
OBJC_ASSOCIATION_ASSIGN |
); |
[array release]; |
|
} |
return 0; |
} |