Reputation: 2032
If I use objc_setAssociatedObject/objc_getAssociatedObject inside a category implementation to store a simulated instance variable in a setter method, how would I access the key in the getter method since any variables declared in the setter method would be outside the scope of the getter method?
Edit: To clarify, if I were to use the following pattern, where should I declare STRING_KEY so that I could use it in both the setter and the getter method.
@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
Upvotes: 67
Views: 38191
Reputation: 29886
For associated storage void *
keys, I like this way of doing it:
static void * const kMyAssociatedStorageKey = (void*)&kMyAssociatedStorageKey;
This avoids having another constant string in the executable, and by setting its value to the address of itself, you get good uniquing, and const
behavior (so you'll nominally get a compiler complaint if you do something that would change the value of the key), without any extra unnecessary exported symbols.
Upvotes: 23
Reputation: 92335
The accepted answer already got it right, but I'd like to add an explanation: the point of the "key" is to get a unique (per process/program) identifier that will not have any accidental collisions.
Imagine the key being declared as NSUInteger
: lots of people would use standard values like 0
, 1
or 42
. Especially if you're using some library or framework, collisions are likely.
So instead, some clever Apple engineer had the idea to declare the key to be a pointer with the intent that you declare a variable and pass the pointer to that variable as a key. The linker will assign that variable an address that is unique within your application and so you are guaranteed to get a unique, collision free value.
In fact, you can pass any value you like, the pointer is not dereferenced (I've tested that). So passing (void *)0
or (void *)42
as the key does work, although it's not a good idea (so please don't do that). For objc_set/getAssociatedObject
, all that matters is that the value passed as key is unique within your process/program.
Upvotes: 6
Reputation: 22962
Quite close. Here is a full example.
.h-file
@interface NSObject (ExampleCategoryWithProperty)
@property (nonatomic, retain) NSArray *laserUnicorns;
@end
.m-file
#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
Just like a normal property - accessible with dot-notation
NSObject *myObject = [NSObject new];
myObject.laserUnicorns = @[@"dipwit", @"dipshit"];
NSLog(@"Laser unicorns: %@", myObject.laserUnicorns);
Easier syntax
Alternatively you could use @selector(nameOfGetter)
instead of creating a static pointer. Why? See https://stackoverflow.com/a/16020927/202451. Example:
- (NSArray *)laserUnicorns {
return objc_getAssociatedObject(self, @selector(laserUnicorns));
}
- (void)setLaserUnicorns:(NSArray *)unicorns {
objc_setAssociatedObject(self, @selector(laserUnicorns), unicorns, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
Upvotes: 10
Reputation: 2183
I know that this question is quit old but I think for completeness there is another way how to use associated objects worth mentioning. This solution utilizes @selector
and therefore there is no need for any extra variable or constant.
@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
(inspired by http://www.tuaw.com/2013/04/10/devjuice-better-objective-c-associated-objects/)
Upvotes: 38
Reputation: 646
Declare a static variable so that you can use its address as the key. The call to objc_setAssociatedObject takes a void* and only the address of your static variable is actually used, not the contents of a NSString... that is only wasting memory.
You just need to add:
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
Upvotes: 63
Reputation: 44321
Declare a static (compilation unit-scope) variable at the top level of the source file. It may help to make it meaningful, something like this:
static NSString *MYSimulatedString = @"MYSimulatedString";
Upvotes: 17