Lazy Properties Revisited
November 16, 2011

A while ago I blogged about how I’d really like Objective-C to have built in support for lazy properties.

My ideal solution would be something like this:

@property (nonatomic, retain, lazy) NSString* name;

The synthesized getter for this property would automatically call a method

 - (NSString*)nameInit 

when the property was first accessed, and would use the resultant value to initialise the property.

This has some advantages over any hand-rolled solution:

In the absence of this solution in the language, I offered a rather complicated set of macros to attempt to implement something similar.

Since then I’ve thought about it a bit more, and come up with another couple of ways of tackling the same task… What Do We Want To Do? ———————-

Let’s say we’ve got a class:

@interface MyClass : NSObject

@property (nonatomic, retain) NSString* name;

@end

and we want to make the name property lazy.

What we need to do is to somehow insert some checking code into the -(NSString*)name getter function, that does something like this:

id value = self.name; // <-- this is a call to the ‘original’ getter
if (!value)
{
    value = [self nameInit];
    self.name = value;
}

return value;

Note that we need to be able to call the original getter here. We could attempt to access the underlying ivar directly, but using the original getter is much better as it preserves the property semantics regarding retain, copy etc.

Using Inheritance

One clean way to do this would be to insert a class in-front of our class, and implement our modified getter there. That allows us to cleanly call on to the original getter.

So we end up with something like this in our header file:

@interface MyClass : NSObject

@property (nonatomic, retain) NSString* name;

@end

@interface MyClassLazy : MyClass
@end

And this in our implementation file:

@implementation MyClass
@synthesize name;
// normal implementation here

// lazy initialisation function for name property
- (NSString*)nameInit
{
    return @"blah";
}
@end


@implementation MyClassLazy

- (NSString*)name
{
    id value = [super name];
    if (!value)
    {
        value = [self nameInit];
        self.name = value;
    }

    return value;
}

@end

Getting Things The Right Way Round

That’s quite neat, but there’s a problem. Our class is called MyClass, but in order to get the lazy behaviour, we need to use the class called MyClassLazy.

We can fix this with a bit of renaming. The header now looks like

@interface MyClassNonLazy : NSObject

@property (nonatomic, retain) NSString* name;

@end

@interface MyClass : MyClassNonLazy
@end

And the implementation:

@implementation MyClass
@synthesize name;
// normal implementation here

// lazy initialisation function for name property
- (NSString*)nameInit
{
    return @"blah";
}
@end


@implementation MyClass

- (NSString*)name
{
    id value = [super name];
    if (!value)
    {
        value = [self nameInit];
        self.name = value;
    }

    return value;
}

@end

Wrapping It Up Nicely

So that, in a nutshell, is a solution that works. With a bit of judicious macro definition, we can generalise this:

#define lazy_interface(class,parent) interface class##_nonlazy : parent
#define end_lazy_interface(class) end @interface class : class##_nonlazy @end

#define lazy_implementation(class) implementation class##_nonlazy
#define lazy_properties(class) end @implementation class
#define end_lazy_implementation(class) end

#define lazy_synthesize(prop) \
    class Dummy__; \
    - (NSString*)prop \
    { \
    id value = [super prop]; \
    if (!value) \
    { \
    value = [super prop##Init]; \
    self.test = value; \
    } \
    \
    return value; \
    }

So now, we can do this in the header:

@lazy_interface(TestClass, NSObject)

@property (nonatomic, retain) NSString* name;

@end_lazy_interface(TestClass)

And this in the implementation:

@lazy_implementation(TestClass)

@synthesize name;

// lazy initialisation function for name property
- (NSString*) nameInit
{
    return @"blah";
}

@lazy_properties(TestClass)

@lazy_synthesize(name)

@end_lazy_implementation(TestClass)

Can We Do Better?

Which is kind of nifty, but still, well, ugly. All our nasty macros make it not look like normal Objective-C.

So how about a different approach? Tune in to my next post

« Lazy Properties Revisited (Again) Signing nested app bundles »
Got a comment on this post? Let me know at @samdeane@mastodon.org.uk.