Born Sleepy - Sam Deane's blog

  • about
  • code
  • music
  • contact
  • elegant chaos
Home › Blogs › Sam Deane's blog

Lazy Properties Dynamic Implementation

Sam Deane — Wed, 2011-11-16 13:43

In my previous post I showed a relatively clean way to implement lazy properties generically using the dynamic runtime.

So how does this dynamic implementation actually work?

The interface is pretty small:

@interface NSObject(ECLazyProperties)

+ (void)initializeLazyProperties;

#define lazy_synthesize(name,init) \
class Dummy__; \
- (id)name##Init__ \
{ \
id value = [self name##Init__]; \
if (!value) \
{ \
    value = (init); \
    [self setValue:value forKey:@#name]; \
} \
return value; \
}

#define lazy_synthesize_method(name,method) lazy_synthesize_value(name,[self method])

@end

Most of the work here is in one macro.

Essentially what this does is define a lazy initialisation method for a property. The method:

  • calls on to the original property getter
  • if it's got a value, we just return it
  • if not, we first KVC to update the original property with its initial value

We define the name of the method by appending Init__ to the property name. We could have appended just Init, but this method really shouldn't be called publicly, so I went for a slightly more obscure name.

How it calls the original getter is kind of interesting. If you unpick the macro you'll see that it appears to be calling itself. Isn't this going to lead to some hideous recursion?

The answer is no - because the method will have been swizzled. When we're actually running in the method, it will be as a result of a call to the original getter method. So if we want to call the original getter method, we need to actually call the init method instead!

If we do discover that the value is uninitialised, we then use KVC to call the proper setter with an initial value.

So all of this relies on some swizzling. How do we set that up?

The implementation of initializeLazyProperties looks like this:

+ (void)initializeLazyProperties
{
    uint count;
    objc_property_t* properties = class_copyPropertyList(self, &count);
    for (int i = 0; i < count ; i++)
    {
        const char* propertyName = property_getName(properties[i]);
        SEL init = NSSelectorFromString([NSString stringWithFormat:@"%sInit__", propertyName]);
        if ([self instancesRespondToSelector:init])
        {
            SEL getter = NSSelectorFromString([NSString stringWithFormat:@"%s", propertyName]);
            Method initMethod = class_getInstanceMethod(self, init);
            Method getterMethod = class_getInstanceMethod(self, getter);

            method_exchangeImplementations(initMethod, getterMethod);
        }
    }
    free(properties);
}

We iterate through a list of properties, looking to see if there is a corresponding lazy initialise method for it. If there is, we simply swizzle the original getter method with the init method.

When the user calls the getter, they'll actually get the init method. This will call on to the getter, check the value, and initialise if necessary.

Complex Initialisation

In the example, we're just supplying a fixed initial value, but of course the real point of lazy initialisation is for situations where we need to do something time consuming in the initialisation, and we only want to actually do it if we have to.

For this situation you can use the second macro - lazy_synthesize_method. Instead of passing in an initial value, you pass in a method to call to return the value.

Conclusions

This code seems to actually work, but I've not tested it intensively to see whether it behaves property in all situations - for example with KVC/KVO.

I think it should do, but right now I view it largely as a thought experiment.

Right back at the beginning I said that the way I really think it should work is

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

I still think that would be ideal. I think that what I've done illustrates that it ought to be entirely possible to add a hack to Clang to implement this, but I leave that as an exercise for the reader...

If anyone is interested in the full source code to my implementation, you'll find it in the ECCore module of my open source ECFoundation library.

[Updated Feb 2012 to fix some broken links]

Login

login or register

Navigation

  • Search
  • Recent posts

Recent Tweets

Recent Tunes

Recent Posts

  • Birthdays not showing in Lion iCal
  • Why Facebook Is Broken For Me
  • NSConference 2012
  • Radar? GTFO!
  • Is it just me?
  • Comments
  • What The Hell Happened To Xcode?
  • The Run Loop In Cocoa Unit Tests
  • Parameterised Unit Tests
  • Git Repository Migration
more

Links

  • Sam Deane's CV
  • Sam Deane on Facebook
  • Sam Deane on MySpace
  • Sam Deane In The Real World
  • Rangers Till I Die
  • UK Mac Developers Group
  • Mountain River Tai Chi Chuan
  • National Secular Society
  • Halflight Music
  • Alien Jones
  • Keith Deane Gardening

Syndicate content

  • about
  • code
  • music
  • contact
  • elegant chaos