避免关联对象键的额外静态变量

时间:2013-04-15 17:11:59

标签: objective-c objective-c-runtime associated-object

使用关联对象时,从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/拍摄的例子)

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

3 个答案:

答案 0 :(得分:49)

根据Erica Sadun的这个blog entry(其归功于Gwynne Raskind),有。{/ p>

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来实现这一目标

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

答案 1 :(得分:6)

如果你需要从单个方法范围之外访问密钥,那么一个很好的模式会导致代码更易读,就是创建一个指针,它只是指向堆栈中自己的地址。例如:

static void const *MyAssocKey = &MyAssocKey;

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

objc_setAssociatedObject(obj, _cmd, associatedObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

答案 2 :(得分: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 of 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的变化(尽管我认为这不是一个合理的担忧)。