objc_setAssociatedObject

Published on — Filed under cocoade

Today I learned about this "hidden" gem of the Objective-C runtime: objc_setAssociatedObject() and its counterpart, obcj_getAssociatedObject().

In short, it allows you to dynamically add properties to instances, just like you'd normally do with the @property() directive.

Why would you care? Here's a simple use case…

Extending UIAlertView to use a completion block instead of a delegate

A naive approach would be subclassing UIAlertView and adding a completion block property:

@interface BetterAlertView : UIAlertView
@property (copy, nonatomic) void (^completion)(NSInteger buttonIndex);
@end

We'd also manually implement the setter to make the alert view a delegate of itself:

@interface BetterAlertView () <UIAlertViewDelegate>
@end

@implementation BetterAlertView

- (void)setCompletion:(void (^)(NSInteger buttonIndex))completion {
  _completion = completion;
  self.delegate = self;
}

- (void)alertView:(UIAlertView *)alertView
didDismissWithButtonIndex:(NSInteger)buttonIndex {
  if (_completion != nil) _completion(buttonIndex);
}

@end

All fine with this approach... But it would be ~6.28 times cooler if we could just do this on UIAlertView itself.

Enter Objective-C associative references

Remember that annoying thing about categories not having ivars? Well...

@interface UIAlertView (Block) <UIAlertViewDelegate>
- (void)setCompletion:(void (^)(NSInteger buttonIndex))completion;
@end
#import <objc/runtime.h>

@implementation UIAlertView (Block)

// no value required, we just need a mem address
static id kUIAlertView_BlockKey;

- (void)setCompletion:(void (^)(NSInteger buttonIndex))completion {
  self.delegate = self;
  // calling objc_setAssociatedObject with OBJC_ASSOCIATION_COPY_NONATOMIC
  // is the same as assigning the completion block to a
  // @property(copy, nonatomic) field.
  objc_setAssociatedObject(self, &kUIAlertView_BlockKey,
                           completion, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (void)alertView:(UIAlertView *)alertView
didDismissWithButtonIndex:(NSInteger)buttonIndex {
  void (^completion)(NSInteger buttonIndex);
  completion = objc_getAssociatedObject(self, &kUIAlertView_BlockKey);

  if (completion != nil) completion(buttonIndex);
}

@end

Now that's some delicious bass!

I've recently added this neat trick to BBBootstrap. You should take a look at it, contains a ton of other neat sugar coating and time savers on top of Foundation and UIKit frameworks.