2

I have a lot of methods that repeat this simple boilerplate:

- (id)myObject {
    if(!_myObject) {
        self.myObject = [_myObject.class new];
    }
    return _myObject;
}

So I want to replace this with a simple macro:

#define default_init(instance) \
    if(!instance) instance = [instance.class new]; \
    return instance;

So that I would only have to call:

- (id)myObject {
        default_init(_myObject);
}

The above code currently compiles, but the issue is that the macro directly sets the instance variable's value. Instead, I'd like to call self.instance = value;

So instead of

if(!instance) instance = [instance.class new];

I'd like something like;

if(!instance) self.instance = [instance.class new];

But obviously the current code does not allow for this. How might I accomplish something like this?

Snowman
  • 31,411
  • 46
  • 180
  • 303

3 Answers3

1

With this macro:

#define default_init(class, instance)      \
    if ( ! _##instance ) {                 \
        self.instance = [class new] ;      \
    }                                      \
    return _##instance

I was able to create this instance method:

- (NSMutableArray*) myObject {
    default_init(NSMutableArray, myObject) ;
}

I had to add a parameter defining the class, because _myObject is still nil, therefore _myObject.class is nil.

This StackOverflow question and this Cprogramming page recommend wrapping your multi-line macro in do {...} while(0):

#define default_init(class, instance)          \
    do {                                       \
        if ( ! _##instance ) {                 \
            self.instance = [class new] ;      \
        }                                      \
        return _##instance ;                   \
    } while(0)

If you really wanted to, you could make a macro that defines the entire method:

#define default_getter(class, instance)        \
    - (class*) instance {                      \
        if ( ! _##instance ) {                 \
            self.instance = [class new] ;      \
        }                                      \
        return _##instance ;                   \
    }

And use it thusly:

default_getter(NSMutableArray, myObject)
Community
  • 1
  • 1
John Sauer
  • 4,411
  • 1
  • 25
  • 33
  • 1
    This looks pretty good. Though just for the sake of the challenge, I wanted to figure out a way to do it without passing in a class - just the property. I've found a way to do this, but it does require an outside library. Check out the answer I posted. – Snowman Mar 23 '13 at 16:48
0

Instead of a macro to build a getter method, I usually declare the property:

Instance.h

@interface Instance : NSObject
@property NSMutableArray* myObject ;
@end

and override - init to initialize the property:

Instance.m

- (id) init {
    self = [super init] ;
    if ( self ) {
        self.myObject = [NSMutableArray new] ;
    }
    return self ;
}
John Sauer
  • 4,411
  • 1
  • 25
  • 33
0

Ok, this is my take on it.

Note: this was done just to see if it could be done. I don't think it would be a good idea to use this in shipping code.

First import #import "EXTRuntimeExtensions.h" from libextobjc. Then:

#define underize(name) _##name

#define property_type(property) \
    property_attr(property)->objectClass

#define property_attr(propertyName) \
    ext_copyPropertyAttributes(class_getProperty(self.class, # propertyName))

#define default_init(propertyName) \
    - (id)propertyName { \
       if(!underize(propertyName)) self.propertyName = [property_type(propertyName) new]; \
       return underize(propertyName); \
} \

Then say you have a property:

@property (nonatomic) NSArray *myArray;

You can do:

default_init(myArray);

And that creates a default getter.

Snowman
  • 31,411
  • 46
  • 180
  • 303