如何在对象内部使用objc_setAssociatedObject/objc_getAssociatedObject?(这是一个关于IT技术的提问标题)

66
如果我在类别实现中使用 objc_setAssociatedObject/objc_getAssociatedObject 来在setter方法中存储一个模拟的实例变量,由于在setter方法中声明的任何变量都会超出getter方法的范围,那么我该如何在getter方法中访问键?
编辑:为了澄清,如果我使用以下模式,那么我应该在哪里声明STRING_KEY,以便在setter和getter方法中都可以使用它。
@interface NSView (simulateVar)
-(void)setSimualtedString:(NSString *)myString;
-(NSString *)simulatedString;
@end

@implementation NSView (simulateVar)

-(void)setSimualtedString: (NSString *)myString
{
    objc_setAssociatedObject(self, &STRING_KEY, myString, OBJC_ASSOCIATION_RETAIN);
}

-(NSString *)simulatedString
{
    return (NSString *)objc_getAssociatedObject(self, &STRING_KEY);
}

@end
6个回答

63

声明一个静态变量,以便您可以将其地址用作键。

调用objc_setAssociatedObject时需要一个void*参数,实际上只使用静态变量的地址,而不是NSString的内容......这只会浪费内存。

您只需要添加:

static char STRING_KEY; // global 0 initialization is fine here, no 
                        // need to change it since the value of the
                        // variable is not used, just the address

1
上面的答案最初引用了一个苹果文档的链接,但该链接已不存在。这里提供了一个示例,说明了上面的答案,尽管它不是在类别setter/getter上下文中。如果有更好的示例,请分享!由于某种原因,关于associatedObjects的主题在网络上并没有得到广泛涵盖。 - abbood
1
我认为这是一个非常接近OP所询问的示例:https://github.com/mystcolor/AFNetworking-ProxyQueue/blob/master/AFHTTPClient%2BProxyQueue.m#L149 - qix
1
另一个不错的键是选择器 - 我已经使用了一段时间,我在某个博客上读到了这个(Mike Ash?Mattt Thompson?)。 - David H

38

我知道这个问题很旧,但是为了完整性,还有另一种使用相关对象的方式值得一提。这种解决方案利用了 @selector ,因此不需要任何额外的变量或常量。

@interface NSObject (CategoryWithProperty)

@property (nonatomic, strong) NSObject *property;

@end

@implementation NSObject (CategoryWithProperty)

- (NSObject *)property {
    return objc_getAssociatedObject(self, @selector(property));
}

- (void)setProperty:(NSObject *)value {
    objc_setAssociatedObject(self, @selector(property), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

(灵感来源于http://www.tuaw.com/2013/04/10/devjuice-better-objective-c-associated-objects/)


2
谢谢您提供的解决方案!对我来说似乎是最清晰的。 - user289841

23

对于关联存储 void * 键,我喜欢这种做法:

static void * const kMyAssociatedStorageKey = (void*)&kMyAssociatedStorageKey; 

这样可以避免在可执行文件中再次使用另一个常量字符串,通过将其值设置为其本身的地址,您可以获得很好的唯一性和const行为(因此,如果执行可能更改键值的操作,则会名义上收到编译器的投诉),而无需任何额外的不必要的导出符号。


17

在源文件的顶层声明一个静态(编译单元范围)变量。最好让它有意义,类似于这样:

static NSString *MYSimulatedString = @"MYSimulatedString";

我用一个回答替换了我的答案,现在我知道它是什么了 :-) - Nicholas Riley
谢谢!这似乎很明显。看来我的大脑想要封装一切 :)。 - leo
2
由于这个答案被取消了接受,为了澄清:变量的值有意义是很好的,因为即使您没有将调试符号编译到程序中,您仍然可以从调试器中打印它。 - Nicholas Riley

10

非常接近,这里提供一个完整的示例。

.h文件

@interface NSObject (ExampleCategoryWithProperty)

@property (nonatomic, retain) NSArray *laserUnicorns;

@end

.m文件

#import <objc/runtime.h>

static void * LaserUnicornsPropertyKey = &LaserUnicornsPropertyKey;

@implementation NSObject (ExampleCategoryWithProperty)

- (NSArray *)laserUnicorns {
    return objc_getAssociatedObject(self, LaserUnicornsPropertyKey);
}

- (void)setLaserUnicorns:(NSArray *)unicorns {
    objc_setAssociatedObject(self, LaserUnicornsPropertyKey, unicorns, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
}

@end

就像普通属性一样 - 可以通过点标记访问

NSObject *myObject = [NSObject new];
myObject.laserUnicorns = @[@"dipwit", @"dipshit"];
NSLog(@"Laser unicorns: %@", myObject.laserUnicorns);

更简单的语法

或者,您可以使用@selector(nameOfGetter)而不是创建静态指针。为什么?请参见https://dev59.com/OWQo5IYBdhLWcg3wfPUa#16020927。例如:

- (NSArray *)laserUnicorns {
    return objc_getAssociatedObject(self, @selector(laserUnicorns));
}

- (void)setLaserUnicorns:(NSArray *)unicorns {
    objc_setAssociatedObject(self, @selector(laserUnicorns), unicorns, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
}

最佳答案!只有这个解决了我的问题。 - Paula Vasconcelos Gueiros

6
接受的答案已经说得很清楚了,但我想补充一下解释: “key”的作用是获得一个唯一的(每个进程/程序)标识符,不会发生任何意外的冲突。
想象一下,如果将“key”声明为NSUInteger:许多人将使用诸如0、1或42之类的标准值。特别是如果您正在使用某些库或框架,则可能会发生冲突。
因此,一些聪明的苹果工程师的想法是将“key”声明为指针,并打算声明一个变量并将指向该变量的指针作为关键字传递。链接器将为该变量分配一个在应用程序中唯一的地址,因此您可以确保获得一个唯一的、无冲突的值。
实际上,您可以传递任何值,指针不会被解引用(我已经测试过)。因此,将(void *)0或(void *)42作为关键字传递确实可行,尽管这不是一个好主意(所以请不要这样做)。对于objc_set/getAssociatedObject,重要的是作为关键字传递的值在您的进程/程序中是唯一的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接