Recently, I was trying to solve a minor programming task in an iOS project. I had to show a warning, an UIAlertView, and needed to remember what this alert was about. One way to solve this is to store the object that the alert is about in a variable. However, that doesn't work when the alert could appear multiple times, warning about things for different objects.
Unfortunately there is no userInfo field at the UIAlertView that you could use to attach the object to the alert. So I looked for an elegant solution and found something interesting: Associative References, via objc_setAssociatedObject. It's available since iOS 3.1 and Mac OS X 10.6 Snow Leopard. Here's what it does:
You can attach or "associate" an object (let's call it payload) with another (let's call that one target), so that when the target finally is deallocated the payload is released. It's a bit like dynamically creating an instance variable at the target object. Here's an example on how to use it (it's not the whole file to keep it short):
static char key; // Key for association. Will be explained in the text below.
alert = [[UIAlertView alloc] initWithTitle:@"Dummy title"
objc_setAssociateObject(alert, &key, payload, OBJC_ASSOCIATION_RETAIN);
- (void)alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)buttonIndex
payload = objc_getAssociatedObject(alert, &key);
NSLog(@"Payload was: %@", payload);
Let's discuss what this does step by step, then afterwards I'll explain the call to objc_setAssociatedObject.
In showAlertWithObject:, an UIAlertView is created. A payload object is associated with that alert and the alert is shown. Once the user presses the OK button, the delegate method alertView:clickedButtonAtIndex: is called. Here the payload is queried again from the alert and then printed to the console.
So here's the call to objc_setAssociatedObject explained:
Three arguments are easy to explain: The first argument is the target object, the third argument is the payload object. The fourth argument sets the policy, that is how the payload is attached to the target: either through a "weak" reference (OBJC_ASSOCIATION_ASSIGN), by a "strong" reference (OBJC_ASSOCIATION_RETAIN) or by copy (OBJC_ASSOCIATION_COPY). The later two are also available as non-atomic variants.
What really needs to be explained is the second argument: the key. Since one might associate several objects (payloads) with one target, we need to be able to distinguish them later. For that a key is needed. In the case of objc_setAssociatedObject, the key is an address. It doesn't matter what the address points to, it doesn't get dereferenced. The whole point is to have a unique key without collisions and by using the address of a static variable this is always guaranteed even with a thousand libraries all trying to use objc_setAssociatedObject on the same target.
So what happens if you call objc_setAssociatedObject a second time on the same target, but with a new payload? If the policy is OBJC_ASSOCIATION_ASSIGN, the new payload is simply associated and the old payload is not touched at all. For OBJC_ASSOCIATION_RETAIN and OBJC_ASSOCIATION_COPY the new payload is associated and the old payload is released. If you want to release the payload without assigning a new payload you simply pass nil as payload in objc_setAssociatedObject. Be careful not to use objc_removeAssociatedObjects as this will clear all associations, not only the ones you have made!
I've created a small test file for you to play with if you like. Just create new command line Mac project and replaced its main.m with this one.
This technique is also useful to simulate additional instance variables for objects that you cannot modify (which is probably the prime use case for which Apple created object associations). As an example, let's assume we want to be able to set a title on an UIImage. Here's how this could be solved:
@property(nonatomic, copy) NSString *title;
static char titleKey;
- (NSString *)title
return objc_getAssociatedObject(self, &titleKey);
- (void)setTitle:(NSString *)title
objc_setAssociatedObject(self, &titleKey, title, OBJC_ASSOCIATION_COPY);