使用关联对象时,从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/拍摄的例子)
是否有更简洁的方法来定义关联的对象键,这不涉及额外变量的声明?
答案 0 :(得分:49)
根据Erica Sadun的这个blog entry(其归功于Gwynne Raskind),有。{/ p>
objc_getAssociatedObject
和objc_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的变化(尽管我认为这不是一个合理的担忧)。