title | author | category | excerpt | status | ||
---|---|---|---|---|---|---|
Associated Objects |
Mattt Thompson |
Objective-C |
Associated Objects is a feature of the Objective-C 2.0 runtime, which allows objects to associate arbitrary values for keys at runtime. It's dark juju, to be handled with as much caution as any other function from objc/runtime.h |
|
#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_setAssociatedObject
objc_getAssociatedObject
objc_removeAssociatedObjects
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 (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end
@implementation NSObject (AssociatedObject)
@dynamic associatedObject;
- (void)setAssociatedObject:(id)object {
objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
return objc_getAssociatedObject(self, @selector(associatedObject));
}
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 kAssociatedObjectKey;
objc_getAssociatedObject(self, &kAssociatedObjectKey);
However, a much simpler solution exists: just use a selector.
Since SELs are guaranteed to be unique and constant, you can use _cmd as the key for objc_setAssociatedObject(). #objective-c #snowleopard
— Bill Bumgarner (@bbum) August 28, 2009
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
Values can be associated onto objects according to the behaviors defined by the enumerated type objc_AssociationPolicy
:
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.
According to the Deallocation Timeline described in WWDC 2011, Session 322 (~36:00), associated objects are erased surprisingly late in the object lifecycle, in
object_dispose()
, which is invoked byNSObject -dealloc
.
One may be tempted to call objc_removeAssociatedObjects()
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.
- 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. For example, AFNetworking uses associated objects on its
UIImageView
category to store a request operation object, used to asynchronously fetch a remote image at a particular URL. - 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. To go back to the previous example of AFNetworking, its category on
UIImageView
, itsimageResponseSerializer
allows image views to optionally apply a filter, or otherwise change the rendering of a remote image before it is set and cached to disk. - 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.
- 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
UITableViewCell
, for use intableView:accessoryButtonTappedForRowWithIndexPath:
, when this can retrieved by callingcellForRowAtIndexPath:
. - 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.