Instance Variables for Objective C Categories Instance Variables for Objective C Categories objective-c objective-c

Instance Variables for Objective C Categories


Yes you can do this, but since you're asking, I have to ask: Are you absolutely sure that you need to? (If you say "yes", then go back, figure out what you want to do, and see if there's a different way to do it)

However, if you really want to inject storage into a class you don't control, use an associative reference.


Recently, I needed to do this (add state to a Category). @Dave DeLong has the correct perspective on this. In researching the best approach, I found a great blog post by Tom Harrington. I like @JeremyP's idea of using @property declarations on the Category, but not his particular implementation (not a fan of the global singleton or holding global references). Associative References are the way to go.

Here's code to add (what appear to be) ivars to your Category. I've blogged about this in detail here.

In File.h, the caller only sees the clean, high-level abstraction:

@interface UIViewController (MyCategory)@property (retain,nonatomic) NSUInteger someObject;@end

In File.m, we can implement the @property (NOTE: These cannot be @synthesize'd):

@implementation UIViewController (MyCategory)- (NSUInteger)someObject{  return [MyCategoryIVars fetch:self].someObject;}- (void)setSomeObject:(NSUInteger)obj{  [MyCategoryIVars fetch:self].someObject = obj;}

We also need to declare and define the class MyCategoryIVars. For ease of understanding, I've explained this out of proper compilation order. The @interface needs to be placed before the Category @implementation.

@interface MyCategoryIVars : NSObject@property (retain,nonatomic) NSUInteger someObject;+ (MyCategoryIVars*)fetch:(id)targetInstance;@end@implementation MyCategoryIVars@synthesize someObject;+ (MyCategoryIVars*)fetch:(id)targetInstance{  static void *compactFetchIVarKey = &compactFetchIVarKey;  MyCategoryIVars *ivars = objc_getAssociatedObject(targetInstance, &compactFetchIVarKey);  if (ivars == nil) {    ivars = [[MyCategoryIVars alloc] init];    objc_setAssociatedObject(targetInstance, &compactFetchIVarKey, ivars, OBJC_ASSOCIATION_RETAIN_NONATOMIC);    [ivars release];  }   return ivars;}- (id)init{  self = [super init];  return self;}- (void)dealloc{  self.someObject = nil;  [super dealloc];}@end

The above code declares and implements the class which holds our ivars (someObject). As we cannot really extend UIViewController, this will have to do.


I believe it is now possible to add synthesized properties to a category and the instance variables are automagically created, but I've never tried it so I'm not sure if it will work.

A more hacky solution:

Create a singleton NSDictionary which will have the UIViewController as the key (or rather its address wrapped as an NSValue) and the value of your property as its value.

Create getter and setter for the property that actually goes to the dictionary to get/set the property.

@interface UIViewController(MyProperty)@property (nonatomic, retain) id myProperty;@property (nonatomic, readonly, retain) NSMutableDcitionary* propertyDictionary;@end@implementation  UIViewController(MyProperty)-(NSMutableDictionary*) propertyDictionary{    static NSMutableDictionary* theDictionary = nil;    if (theDictionary == nil)    {        theDictioanry = [[NSMutableDictionary alloc] init];    }    return theDictionary;}-(id) myProperty{    NSValue* key = [NSValue valueWithPointer: self];    return [[self propertyDictionary] objectForKey: key];}-(void) setMyProperty: (id) newValue{    NSValue* key = [NSValue valueWithPointer: self];    [[self propertyDictionary] setObject: newValue forKey: key];    }@end

Two potential problems with the above approach:

  • there's no way to remove keys of view controllers that have been deallocated. As long as you are only tracking a handful, that shouldn't be a problem. Or you could add a method to delete a key from the dictionary once you know you are done with it.
  • I'm not 100% certain that the isEqual: method of NSValue compares content (i.e. the wrapped pointer) to determine equality or if it just compares self to see if the comparison object is the exact same NSValue. If the latter, you'll have to use NSNumber instead of NSValue for the keys (NSNumber numberWithUnsignedLong: will do the trick on both 32 bit and 64 bit platforms).