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

Gab*_*lla 22 objective-c objective-c-runtime associated-object

当使用关联对象,从iOS 4和OSX 10.6开始提供Objective-C运行时功能时,必须定义用于在运行时存储和检索对象的密钥.

典型的用法是定义如下的密钥

static char const * const ObjectTagKey = "ObjectTag";
Run Code Online (Sandbox Code Playgroud)

然后使用是存储对象

objc_setAssociatedObject(self, ObjectTagKey, newObjectTag, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
Run Code Online (Sandbox Code Playgroud)

并检索它

objc_getAssociatedObject(self, ObjectTagKey);
Run Code Online (Sandbox Code Playgroud)

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

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

Gab*_*lla 49

根据Erica Sadun的博客文章(其归功于Gwynne Raskind),有.

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

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

因此,我们可以摆脱密钥声明,只需使用我们的属性的选择器地址.

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

@property (nonatomic, retain) id anAssociatedObject;
Run Code Online (Sandbox Code Playgroud)

我们可以为它的getter/setter提供动态实现

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

- (id)anAssociatedObject {
    return objc_getAssociatedObject(self, @selector(anAssociatedObject));
}
Run Code Online (Sandbox Code Playgroud)

非常整洁,绝对比为每个关联对象定义额外的静态变量键更清晰.

这样安全吗?

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

Apple可能不得不实施一个全新的ABI来实现这一目标

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

  • 不,我的意思是[ABI](http://en.wikipedia.org/wiki/Application_binary_interface) (10认同)
  • objc_ {set,get} AssociaetdObject()是objc运行时非常安全的部分. (8认同)

Wil*_*ell 6

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

static void const *MyAssocKey = &MyAssocKey;
Run Code Online (Sandbox Code Playgroud)

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

objc_setAssociatedObject(obj, _cmd, associatedObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
Run Code Online (Sandbox Code Playgroud)


Nat*_*ler 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
Run Code Online (Sandbox Code Playgroud)

然后在我们的类别中的一些子类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
Run Code Online (Sandbox Code Playgroud)

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

使用特别有益

NSStringFromSelector(@selector(anAssociatedObject))
Run Code Online (Sandbox Code Playgroud)

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