23

当使用关联对象(从 iOS 4 和 OSX 10.6 开始提供的 Objective-C 运行时功能)时,有必要定义一个键用于在运行时存储和检索对象。

典型的用法是定义如下的键

static char const * const ObjectTagKey = "ObjectTag";

然后用就是存储对象

objc_setAssociatedObject(self, ObjectTagKey, newObjectTag, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

并检索它

objc_getAssociatedObject(self, ObjectTagKey);

http://oleb.net/blog/2011/05/faking-ivars-in-objc-categories-with-associative-references/的例子)

是否有一种更简洁的方法来定义关联的对象键,而不涉及额外变量的声明?

4

3 回答 3

50

根据Erica Sadun(其功劳归于 Gwynne Raskind )的这篇文,有。

objc_getAssociatedObjectobjc_getAssociatedObject需要一个密钥来存储对象。这样的键需要是一个常量void指针。所以最后我们只需要一个随着时间保持不变的固定地址。

事实证明,该@selector实现提供了我们所需要的,因为它使用固定地址。

因此,我们可以摆脱键声明并简单地使用我们属性的选择器地址。

因此,如果您在运行时关联一个属性,例如

@property (nonatomic, retain) id anAssociatedObject;

我们可以为其 getter/setter 提供动态实现,看起来像

- (void)setAnAssociatedObject:(id)newAssociatedObject {
     objc_setAssociatedObject(self, @selector(anAssociatedObject), newAssociatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (id)anAssociatedObject {
    return objc_getAssociatedObject(self, @selector(anAssociatedObject));
}

比为每个关联对象定义一个额外的静态变量键非常简洁且绝对干净。

这安全吗?

由于这是依赖于实现的,一个合理的问题是:它会很容易崩溃吗?引用博客条目

Apple 可能必须实施一个全新的 ABI 才能做到这一点

如果我们认为这些话是真的,那么它是相当安全的。

于 2013-04-15T17:12:22.390 回答
6

如果您需要从单个方法的范围之外访问密钥,那么一个很好的模式会导致代码更具可读性,即创建一个指针,该指针仅指向它自己在堆栈中的地址。例如:

static void const *MyAssocKey = &MyAssocKey;

如果您只需要从单个方法的范围内访问,您实际上可以只使用_cmd,它保证是唯一的。例如:

objc_setAssociatedObject(obj, _cmd, associatedObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
于 2013-08-31T12:53:50.103 回答
5

@Gabriele Petronella 讨论的想法的一个细微变化是将字典与每个对象相关联:

//NSObject+ADDLAssociatedDictionary.h

#import <Foundation/Foundation.h>

@interface NSObject (ADDLAssociatedDictionary)
- (void)addl_setAssociatedObject:(id)object forKey:(id<NSCopying>)key;
- (id)addl_associatedObjectForKey:(id<NSCopying>)key;
@end

//NSObject+ADDLAssociatedDictionary.m

#import <objc/runtime.h>

@interface NSObject (ADDLAssociatedDictionaryInternal)
- (NSMutableDictionary *)addl_associatedDictionary;
@end

@implementation NSObject (ADDLAssociatedDictionary)

- (void)addl_setAssociatedObject:(id)object forKey:(id<NSCopying>)key
{
    if (object) {
        self.addl_associatedDictionary[key] = object;
    } else {
        [self.addl_associatedDictionary removeObjectForKey:key];
    }
}
- (id)addl_associatedObjectForKey:(id<NSCopying>)key
{
    return self.addl_associatedDictionary[key];
}

@end

@implementation NSObject (ADDLAssociatedDictionaryInternal)
const char addl_associatedDictionaryAssociatedObjectKey;
- (NSMutableDictionary *)addl_associatedDictionaryPrimitive
{
    return objc_getAssociatedObject(self, &addl_associatedDictionaryAssociatedObjectKey);
}
- (void)addl_setAssociatedDictionaryPrimitive:(NSMutableDictionary *)associatedDictionary
{
    objc_setAssociatedObject(self, &addl_associatedDictionaryAssociatedObjectKey, associatedDictionary, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSMutableDictionary *)addl_generateAssociatedDictionary
{
    NSMutableDictionary *associatedDictionary = [[NSMutableDictionary alloc] init];
    [self addl_setAssociatedDictionaryPrimitive:associatedDictionary];
    return associatedDictionary;
}

- (NSMutableDictionary *)addl_associatedDictionary
{
    NSMutableDictionary *res = nil;

    @synchronized(self) {
        if (!(res = [self addl_associatedDictionaryPrimitive])) {
            res = [self addl_generateAssociatedDictionary];
        }
    }

    return res;
}
@end

然后在我们类上的一些子类Derived NSObject

//Derived+Additions.h

#import "Derived.h"

@interface Derived (Additions)
@property (nonatomic) id anAssociatedObject;
@end

//Derived+Additions.m

#import "NSObject+ADDLAssociatedDictionary.h"

@implementation Derived (Additions)
- (void)setAnAssociatedObject:(id)anAssociatedObject
{
    [self addl_setAssociatedObject:anAssociatedObject forKey:NSStringFromSelector(@selector(anAssociatedObject))];
}
- (id)anAssociatedObject
{
    return [self addl_associatedObjectForKey:NSStringFromSelector(@selector(anAssociatedObject))];
}
@end

一般来说,关联字典方法的一个好处是能够为在运行时生成的键设置对象,从而增加了灵活性,更不用说更好的语法了。

使用特定的好处

NSStringFromSelector(@selector(anAssociatedObject))

NSStringFromSelector保证给出NSString选择器的表示,它将始终是可接受的字典键。因此,我们完全不必担心 ABI 的变化(尽管我认为这不是一个合理的担忧)。

于 2013-04-16T04:16:42.947 回答