127

As I cannot create a synthesized property in a Category in Objective-C, I do not know how to optimize the following code:

@interface MyClass (Variant)
@property (nonatomic, strong) NSString *test;
@end

@implementation MyClass (Variant)

@dynamic test;

- (NSString *)test {
    NSString *res;
    //do a lot of stuff
    return res;
}

@end

The test-method is called multiple times on runtime and I'm doing a lot of stuff to calculate the result. Normally using a synthesized property I store the value in a IVar _test the first time the method is called, and just returning this IVar next time. How can I optimized the above code?

hfossli
  • 22,616
  • 10
  • 116
  • 130
dhrm
  • 14,335
  • 34
  • 117
  • 183
  • 2
    Why not do what you normally do, only instead of a category, add the property to a MyClass base class? And to take it further, perform your heavy stuff on the background and have the process fire off a notification or call some handler for MyClass when the process is complete. – Jeremy Jan 04 '12 at 19:59
  • 4
    MyClass is a generated class from Core Data. If I but my custom object code inside the generated class it would disappear if I regenerate the class from my Core Data. Because of this, I'm using a category. – dhrm Jan 04 '12 at 20:28
  • 1
    Maybe accept the question which applies best to the title? ("Property in category") – hfossli Jan 09 '14 at 17:55
  • Why not just create a subclass? – Scott Zhu Mar 08 '17 at 08:17
  • Even the pseudo code in your question is misleading. A category is NOT a (Variant). It is an ADDITION to the functionality of the class. It CAN override existing methods, but that's an edge case with many nor-very-expected-behaviors. – Motti Shneor Jun 20 '22 at 09:43

6 Answers6

178

.h-file

@interface NSObject (LaserUnicorn)

@property (nonatomic, strong) LaserUnicorn *laserUnicorn;

@end

.m-file

#import <objc/runtime.h>

static void * LaserUnicornPropertyKey = &LaserUnicornPropertyKey;

@implementation NSObject (LaserUnicorn)

- (LaserUnicorn *)laserUnicorn {
    return objc_getAssociatedObject(self, LaserUnicornPropertyKey);
}

- (void)setLaserUnicorn:(LaserUnicorn *)unicorn {
    objc_setAssociatedObject(self, LaserUnicornPropertyKey, unicorn, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
}

@end

Just like a normal property - accessible with dot-notation

NSObject *myObject = [NSObject new];
myObject.laserUnicorn = [LaserUnicorn new];
NSLog(@"Laser unicorn: %@", myObject.laserUnicorn);

Easier syntax

Alternatively you could use @selector(nameOfGetter) instead of creating a static pointer key like so:

- (LaserUnicorn *)laserUnicorn {
    return objc_getAssociatedObject(self, @selector(laserUnicorn));
}

- (void)setLaserUnicorn:(LaserUnicorn *)unicorn {
    objc_setAssociatedObject(self, @selector(laserUnicorn), unicorn, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
}

For more details see https://stackoverflow.com/a/16020927/202451

Community
  • 1
  • 1
hfossli
  • 22,616
  • 10
  • 116
  • 130
  • 4
    Good article. One thing to note is an update in the article. __Update December 22, 2011: It’s important to note that the key for the association is a void pointer void *key, not a string. That means that when retrieving an associated reference, you have to pass the exact same pointer to the runtime. It would not work as intended if you used a C string as the key, then copied the string to another place in memory and tried to access the associated reference by passing the pointer to the copied string as a key.__ – Mr Rogers Sep 30 '13 at 17:58
  • 4
    You really don't need the `@dynamic objectTag;`. `@dynamic` means the setter & getter will be generated somewhere else, but in this case they're implemented right here. – IluTov Nov 26 '13 at 11:10
  • @NSAddict true! Fixed! – hfossli Nov 26 '13 at 12:35
  • 1
    How about the dealloc of laserUnicorns for manual memory management? Is this a memory leak? – Manny Jan 15 '14 at 10:21
  • Is released automatically http://stackoverflow.com/questions/6039309/when-does-an-associated-object-get-released – hfossli Jan 27 '14 at 08:43
  • What is the meaning of declaring the property as 'retain'? There is no real instance variable that can be retained. – kernix Feb 24 '14 at 22:15
  • @kernix Well, telling every user of the class wether the object is retained, assigned or copied can be usefull/important information. – hfossli Feb 25 '14 at 08:18
  • @hfossli so this is only used for documentation purposes to reflect the use of OBJC_ASSOCIATION_RETAIN_NONATOMIC under the hood(as there is no actual instance variable). – kernix Feb 25 '14 at 09:10
  • @kernix Correct. Just to be clear about how the memory management is for that object. – hfossli Feb 25 '14 at 09:53
  • Hey, there is a serious issue with this sample: ``static void * LaserUnicornsPropertyKey`` defaults to ``0``, so we are passing ``0`` to objc_setAssociatedObject instead of a unique pointer which is problematic when associating multiple properties this way. Use ``&LaserUnicornsPropertyKey``! (Wanted to edit the post to fix this but my edit got rejected, can someone fix this?) – stefreak Mar 02 '14 at 14:52
  • great answer! however don't forget to `#import `in category .m file otherwise. compile time error: _Implicit declaration of function 'objc_getAssociatedObject' is invalid in C99_ comes up. https://stackoverflow.com/questions/9408934/implicit-declaration-of-function-objc-getassociatedobject-is-invalid-in-c99 – Sathe_Nagaraja Jan 25 '18 at 01:33
127

@lorean's method will work (note: answer is now deleted), but you'd only have a single storage slot. So if you wanted to use this on multiple instances and have each instance compute a distinct value, it wouldn't work.

Fortunately, the Objective-C runtime has this thing called Associated Objects that can do exactly what you're wanting:

#import <objc/runtime.h>

static void *MyClassResultKey;
@implementation MyClass

- (NSString *)test {
  NSString *result = objc_getAssociatedObject(self, &MyClassResultKey);
  if (result == nil) {
    // do a lot of stuff
    result = ...;
    objc_setAssociatedObject(self, &MyClassResultKey, result, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  }
  return result;
}

@end
Cœur
  • 37,241
  • 25
  • 195
  • 267
Dave DeLong
  • 242,470
  • 58
  • 448
  • 498
  • fixed link for Associated Objects: http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/ObjectiveC/Chapters/ocAssociativeReferences.html#//apple_ref/doc/uid/TP30001163-CH24-SW1 – MechEthan Jan 04 '12 at 20:22
  • 6
    Great answer! You can even get rid the static variable using `@selector(test)` as a key, as explained here: http://stackoverflow.com/questions/16020918/avoid-extra-static-variables-for-associated-objects-keys/16020927#16020927 – Gabriele Petronella Nov 07 '13 at 20:13
  • Answer is good, but does not answer how to make the proeprty :) Just how to store values. – hfossli Jan 09 '14 at 16:05
  • @DaveDeLong - How can I do the same using NSInteger or float? – Jassi Apr 15 '15 at 11:34
  • Don't use this if you ever plan on using Swift, because it won't work for Swift. So if you need to use that property in Swift you'll hit a wall. – HotFudgeSunday Oct 03 '16 at 13:49
  • 1
    @HotFudgeSunday - think it does work in swift: http://stackoverflow.com/questions/24133058/is-there-a-way-to-set-associated-objects-in-swift – Scott Corscadden Oct 20 '16 at 16:02
  • @DaveDeLong Why not just create a subclass? – Scott Zhu Mar 08 '17 at 08:15
  • @ScottZhu for many reasons, sometimes you can't subclass. Maybe the objects are created for you. Maybe it's a class cluster. Etc – Dave DeLong Mar 08 '17 at 15:14
34

The given answer works great and my proposal is just an extension to it that avoids writing too much boilerplate code.

In order to avoid writing repeatedly getter and setter methods for category properties this answer introduces macros. Additionally these macros ease the use of primitive type properties such as int or BOOL.

Traditional approach without macros

Traditionally you define a category property like

@interface MyClass (Category)
@property (strong, nonatomic) NSString *text;
@end

Then you need to implement a getter and setter method using an associated object and the get selector as the key (see original answer):

#import <objc/runtime.h>

@implementation MyClass (Category)
- (NSString *)text{
    return objc_getAssociatedObject(self, @selector(text));
}

- (void)setText:(NSString *)text{
    objc_setAssociatedObject(self, @selector(text), text, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

My suggested approach

Now, using a macro you will write instead:

@implementation MyClass (Category)

CATEGORY_PROPERTY_GET_SET(NSString*, text, setText:)

@end

The macros are defined as following:

#import <objc/runtime.h>

#define CATEGORY_PROPERTY_GET(type, property) - (type) property { return objc_getAssociatedObject(self, @selector(property)); }
#define CATEGORY_PROPERTY_SET(type, property, setter) - (void) setter (type) property { objc_setAssociatedObject(self, @selector(property), property, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }
#define CATEGORY_PROPERTY_GET_SET(type, property, setter) CATEGORY_PROPERTY_GET(type, property) CATEGORY_PROPERTY_SET(type, property, setter)

#define CATEGORY_PROPERTY_GET_NSNUMBER_PRIMITIVE(type, property, valueSelector) - (type) property { return [objc_getAssociatedObject(self, @selector(property)) valueSelector]; }
#define CATEGORY_PROPERTY_SET_NSNUMBER_PRIMITIVE(type, property, setter, numberSelector) - (void) setter (type) property { objc_setAssociatedObject(self, @selector(property), [NSNumber numberSelector: property], OBJC_ASSOCIATION_RETAIN_NONATOMIC); }

#define CATEGORY_PROPERTY_GET_UINT(property) CATEGORY_PROPERTY_GET_NSNUMBER_PRIMITIVE(unsigned int, property, unsignedIntValue)
#define CATEGORY_PROPERTY_SET_UINT(property, setter) CATEGORY_PROPERTY_SET_NSNUMBER_PRIMITIVE(unsigned int, property, setter, numberWithUnsignedInt)
#define CATEGORY_PROPERTY_GET_SET_UINT(property, setter) CATEGORY_PROPERTY_GET_UINT(property) CATEGORY_PROPERTY_SET_UINT(property, setter)

The macro CATEGORY_PROPERTY_GET_SET adds a getter and setter for the given property. Read-only or write-only properties will use the CATEGORY_PROPERTY_GET and CATEGORY_PROPERTY_SET macro respectively.

Primitive types need a little more attention

As primitive types are no objects the above macros contain an example for using unsigned int as the property's type. It does so by wrapping the integer value into a NSNumber object. So its usage is analog to the previous example:

@interface ...
@property unsigned int value;
@end

@implementation ...
CATEGORY_PROPERTY_GET_SET_UINT(value, setValue:)
@end

Following this pattern, you can simply add more macros to also support signed int, BOOL, etc...

Limitations

  1. All macros are using OBJC_ASSOCIATION_RETAIN_NONATOMIC by default.

  2. IDEs like App Code do currently not recognize the setter's name when refactoring the property's name. You would need to rename it by yourself.

Lars Blumberg
  • 19,326
  • 11
  • 90
  • 127
  • 1
    don't forget to `#import `in category .m file otherwise. compile time error: _Implicit declaration of function 'objc_getAssociatedObject' is invalid in C99_ comes up. https://stackoverflow.com/questions/9408934/implicit-declaration-of-function-objc-getassociatedobject-is-invalid-in-c99 – Sathe_Nagaraja Jan 25 '18 at 01:36
8

Just use libextobjc library:

h-file:

@interface MyClass (Variant)
@property (nonatomic, strong) NSString *test;
@end

m-file:

#import <extobjc.h>
@implementation MyClass (Variant)

@synthesizeAssociation (MyClass, test);

@end

More about @synthesizeAssociation

Ruslan Mansurov
  • 1,281
  • 16
  • 23
3

Tested only with iOS 9 Example: Adding an UIView property to UINavigationBar (Category)

UINavigationBar+Helper.h

#import <UIKit/UIKit.h>

@interface UINavigationBar (Helper)
@property (nonatomic, strong) UIView *tkLogoView;
@end

UINavigationBar+Helper.m

#import "UINavigationBar+Helper.h"
#import <objc/runtime.h>

#define kTKLogoViewKey @"tkLogoView"

@implementation UINavigationBar (Helper)

- (void)setTkLogoView:(UIView *)tkLogoView {
    objc_setAssociatedObject(self, kTKLogoViewKey, tkLogoView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIView *)tkLogoView {
    return objc_getAssociatedObject(self, kTKLogoViewKey);
}

@end
Rikco
  • 376
  • 5
  • 9
-2

Another possible solution, perhaps easier, which doesn't use Associated Objects is to declare a variable in the category implementation file as follows:

@interface UIAlertView (UIAlertViewAdditions)

- (void)setObject:(id)anObject;
- (id)object;

@end


@implementation UIAlertView (UIAlertViewAdditions)

id _object = nil;

- (id)object
{
    return _object;
}

- (void)setObject:(id)anObject
{
    _object = anObject;
}
@end

The downside of this sort of implementation is that the object doesn't function as an instance variable, but rather as a class variable. Also, property attributes can't be assigned(such as used in Associated Objects like OBJC_ASSOCIATION_RETAIN_NONATOMIC)

kernix
  • 7,970
  • 3
  • 28
  • 44
  • The question asks about instance variable. Your solution is just like Lorean – hfossli Feb 28 '14 at 09:17
  • 1
    Really?? Did you know what you're doing? You created a pair of getter/setter method to access an internal variable which is independent for a file BUT a class object, that is, if you allocate two UIAlertView class objects, the object values of their are the same! – Itachi Oct 17 '14 at 06:13