Associated Objects
#import <objc/runtime.h>
Objective-C developers are conditioned to be wary of whatever follows this ominous incantation. And for good reason: messing with the Objective-C runtime changes the very fabric of reality for all of the code that runs on it.
In the right hands, the functions of <objc/runtime.h>
have the potential to add powerful new behavior to an application or framework, in ways that would otherwise not be possible. In the wrong hands, it drains the proverbial sanity meter of the code, and everything it may interact with (with terrifying side-effects).
Therefore, it is with great trepidation that we consider this Faustian bargain, and look at one of the subjects most-often requested by NSHipster readers: associated objects.
Associated Objects—or Associative References, as they were originally known—are a feature of the Objective-C 2.0 runtime, introduced in OS X Snow Leopard (available in iOS 4). The term refers to the following three C functions declared in <objc/runtime.h>
, which allow objects to associate arbitrary values for keys at runtime:
objc_set
Associated Object objc_get
Associated Object objc_remove
Associated Objects
Why is this useful? It allows developers to add custom properties to existing classes in categories, which is an otherwise notable shortcoming for Objective-C.
@interface NSObject (Associated Object)
@property (nonatomic, strong) id associated Object;
@end
@implementation NSObject (Associated Object)
@dynamic associated Object;
- (void)set Associated Object:(id)object {
objc_set Associated Object(self, @selector(associated Object), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associated Object {
return objc_get Associated Object(self, @selector(associated Object));
}
It is often recommended that they key be a static char
—or better yet, the pointer to one. Basically, an arbitrary value that is guaranteed to be constant, unique, and scoped for use within getters and setters:
static char k Associated Object Key;
objc_get Associated Object(self, &k Associated Object Key);
However, a much simpler solution exists: just use a selector.
Since SEL
s are guaranteed to be unique and constant, you can use _cmd
as the key for objc_set Associated Object()
. #objective-c #snowleopard
— Bill Bumgarner (@bbum) August 28, 2009
Associative Object Behaviors
Values can be associated onto objects according to the behaviors defined by the enumerated type objc_Association
:
Behavior |
@property Equivalent |
Description |
---|---|---|
OBJC_ASSOCIATION_ASSIGN
|
@property (assign) or @property (unsafe_unretained)
|
Specifies a weak reference to the associated object. |
OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
@property (nonatomic, strong)
|
Specifies a strong reference to the associated object, and that the association is not made atomically. |
OBJC_ASSOCIATION_COPY_NONATOMIC
|
@property (nonatomic, copy)
|
Specifies that the associated object is copied, and that the association is not made atomically. |
OBJC_ASSOCIATION_RETAIN
|
@property (atomic, strong)
|
Specifies a strong reference to the associated object, and that the association is made atomically. |
OBJC_ASSOCIATION_COPY
|
@property (atomic, copy)
|
Specifies that the associated object is copied, and that the association is made atomically. |
Weak associations to objects made with OBJC_ASSOCIATION_ASSIGN
are not zero weak
references, but rather follow a behavior similar to unsafe_unretained
, which means that one should be cautious when accessing weakly associated objects within an implementation.
Removing Values
One may be tempted to call objc_remove
at some point in their foray into associated objects. However, as described in the documentation, it’s unlikely that you would have an occasion to invoke it yourself:
The main purpose of this function is to make it easy to return an object to a “pristine state”. You should not use this function for general removal of associations from objects, since it also removes associations that other clients may have added to the object. Typically you should use objc_setAssociatedObject with a nil value to clear an association.
Patterns
Adding private variables to facilitate implementation details
When extending the behavior of a built-in class, it may be necessary to keep track of additional state. This is the textbook use case for associated objects.
Adding public properties to configure category behavior.
Sometimes, it makes more sense to make category behavior more flexible with a property, than in a method parameter. In these situations, a public-facing property is an acceptable situation to use associated objects.
Creating an associated observer for KVO
When using KVO in a category implementation, it is recommended that a custom associated-object be used as an observer, rather than the object observing itself.
Anti-Patterns
Storing an associated object, when the value is not needed
A common pattern for views is to create a convenience method that populates fields and attributes based on a model object or compound value. If that value does not need to be recalled later, it is acceptable, and indeed preferable, not to associate with that object.
Storing an associated object, when the value can be inferred
For example, one might be tempted to store a reference to a custom accessory view’s containing UITable
, for use in table
, when this can retrieved by calling cell
.
Using associated objects instead of X
…where X is any one the following:
- Subclassing for when inheritance is a more reasonable fit than composition.
- Target-Action for adding interaction events to responders.
- Gesture Recognizers for any situations when target-action doesn’t suffice.
- Delegation when behavior can be delegated to another object.
- NSNotification & NSNotificationCenter for communicating events across a system in a loosely-coupled way.
Associated objects should be seen as a method of last resort, rather than a solution in search of a problem (and really, categories themselves really shouldn’t be at the top of the toolchain to begin with).
Like any clever trick, hack, or workaround, there is a natural tendency for one to actively seek out occasions to use it—especially just after learning about it. Do your best to understand and appreciate when it’s the right solution, and save yourself the embarrassment of being scornfully asked “why in the name of $DEITY” you decided to go with that solution.