Objective-C Associated Objects
With iOS 4.0 and Mac OS X 10.6 (Snow Leopard), Apple added some new features to the Objective-C runtime. Among the many new additions is a very handy feature called Associated Objects, which allow you to associate objects at runtime without subclassing or resorting to tedious hacks. While there are many use cases for Associated Objects, we’ll cover a simple, yet common, example of their benefits after the break.
Declared in /usr/include/objc/runtime.h, this is the entirety of Apple’s public headers on the subject:
/* Associated Object support. */
/* objc_setAssociatedObject() options */
enum {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};
typedef uintptr_t objc_AssociationPolicy;
OBJC_EXPORT void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER;
OBJC_EXPORT id objc_getAssociatedObject(id object, void *key)
AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER;
OBJC_EXPORT void objc_removeAssociatedObjects(id object)
AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER;
What are associated objects, and how do you use them? Well, one of the more convenient use cases is to link an object with a button. Let’s say you had a table view with a UIButton in each cell. The button would have a target-action pair, typically calling something like this:
- (IBAction)myButtonPressed:(id)sender;
Suppose you need to do something with the table view cell when this button is pressed. When -myButtonPressed: is called, how do we know what cell the button-press came from? Well, there are a few options. First, we could ask the button for its superview (the cell’s content view) and then that superview for its superview. That’s messy. Second, we could ask the table view for its visible cells, then look through each of them for the button as a subview of the content view. Equally messy. With the new associated objects feature of the Objective-C runtime, it’s as easy as this:
- (UITableViewCell*)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Cell creation code here. “theButton” is the button, and “cell” is the cell.
objc_setAssociatedObject(theButton, "cell", cell, OBJC_ASSOCIATION_ASSIGN);
return cell;
}
// Then, in our action code:
- (IBAction)myButtonPressed:(id)sender
{
UITableViewCell *cell = objc_getAssociatedObject(sender, "cell");
if (cell) {
// Now that we have a pointer to the cell, do something here.
}
}
So what did we do, exactly? When you call objc_setAssociatedObject(), you give it four parameters:
- id object: The object onto which we’re making the association. In this case we use the button since we want to get the cell back from the button.
- void *key: A key to use, much like a key for a dictionary. I recommend using something that you know won’t change; typically, I just make a global const char * and put my key there.
- id value: This is the actual object associated with that key; in our example, the cell.
- objc_AssociationPolicy policy: The value that you give as the association policy defines the memory management. It can be OBJC_ASSOCIATION_ASSIGN, which does no memory management whatsoever, or _RETAIN or _COPY semantics, which will either retain the object or make a copy of it, as well as _NONATOMIC versions of those two.
The runtime keeps track of all associated objects. When you associate an object with a key, it will first remove anything already at that key, using the memory management semantics that it was added with (so if it wasn’t ASSIGN, it’ll send a -release message). If you want to remove an associated object, just call objc_setAssociatedObject() again, just using nil for the object to associate. You can remove all associated objects from an object by using objc_removeAssociatedObjects(id object), but this is discouraged, as some other code may have also associated something.
The memory management is taken care of for you; when an object is deallocated, any associated objects that were copied or retained are released.
So why use this feature of the runtime? Well, in our prior example, we’re associating something with a UIButton. Some people might just think, “Hey, I’ll subclass UIButton and add an instance variable to hold a pointer to the UITableViewCell!” For most classes, that works just fine, but as it turns out, UIButton is a class cluster and a pain in the neck to subclass. All we want to do, though, is store this pointer, so this allows us to do so without modifying UIButton at all. You can do other cool stuff with it, like running code when any object is deallocated, getting around tough memory management situations, and streamlining how your target/action code works. Happy coding!