How do I use objc_setAssociatedObject/objc_getAssociatedObject inside an object?
Asked Answered
V

6

66

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
Viridis answered 17/5, 2010 at 1:24 Comment(0)
H
63

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
Hafnium answered 21/5, 2010 at 13:57 Comment(3)
the answer above initially referenced a link to apple docs that no longer exists.. here is an example that illustrates the answer above, although it's not in a category setter/getter context. If someone has a better example please share! For some reason the topic of associatedObjects isn't covered extensively in the web.Tasker
I think this is a super close example of what the OP is asking about: github.com/mystcolor/AFNetworking-ProxyQueue/blob/master/…Bryon
Another nice key is a selector - I've been using this for a while, I read about this on some blog (Mike Ash? Mattt Thompson?).Biisk
K
38

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/)

Kurdish answered 23/5, 2013 at 7:46 Comment(1)
Thank you for this solution! Seems to be the most clearest for me.Operand
I
23

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.

Islam answered 15/1, 2012 at 16:48 Comment(0)
K
17

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";
Kagoshima answered 17/5, 2010 at 1:32 Comment(3)
I replaced my answer with one that answers your question, now I know what it is :-)Kagoshima
Thanks! This seems obvious enough. It seems my brain wants to encapsulate everything :).Viridis
Since this answer got un-accepted, to clarify: it's nice to have the value of the variable be meaningful because you can then print it from a debugger, even if you don't have debug symbols compiled into your program.Kagoshima
D
10

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://mcmap.net/q/175829/-avoid-extra-static-variables-for-associated-objects-keys. Example:

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

- (void)setLaserUnicorns:(NSArray *)unicorns {
    objc_setAssociatedObject(self, @selector(laserUnicorns), unicorns, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
}
Donia answered 15/2, 2013 at 17:19 Comment(1)
Best answer! Only one that solved the problem for me.Politics
T
6

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.

Topaz answered 24/9, 2013 at 22:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.