Last Updated: February 25, 2016
·
1.848K
· rismay

Really Lazy Instantiation

I looked into a language feature that most Objective C developers overlook: compound statements. The idea is simple: lazily instantiate an instance variable without repeating the instance variable over and over again. Now I write code that looks like this:

- (NSMutableDictionary *) dictionary {
    return WSM_LAZY(_dictionary, ({
        NSMutableDictionary *initDictionary = @{}.mutableCopy;
        initDictionary[@"starterKey"] = @"starterObject";
        NSLog(@"Lazy instantiated dictionary: %@", initDictionary);
        initDictionary;
    }));
}

How did I get here?

In the Beginning There Was - (instancetype)init

One of the first things you learn as an Objective C developer is how to alloc-init a new object. There are all sorts of stylish ways to init an object. However, two variations caught my attention for how similar they looked syntactically, but how differently they worked at a fundamental level.

Example 1: Init in it’s most basic form

- (instancetype) init { //Start using instancetype ASAP
    self = [super init];
    if (self) {
        // Init instance variables... (actually don’t, we’ll get to that later).
    }
    return self;
}

Example 2: It works, but this just doesn’t look right and could get you in trouble.

- (instancetype) init {
    if (self = [super init]) { //If you do this often, you’re going to get caught. 

    }
    return self;
}

Example 3: This is what you meant to do.

- (instancetype) init {
    if ((self = [super init])) { 
        // General init stuff here. 
    }
    return self;
}

In Example 3, the extra parenthesis in the init statement returns the value of the inner expression. Despite using this in almost every init statement, this language property is not used to the fullest advantage in most Objective C code. This got me thinking: How far can we take this pattern? We’ll revisit this soon, but first...

Ternary: This or That

As I mentioned above: don’t use the init statement to instantiate variables. Use lazy instantiation instead. Various init methods could be called and if you don’t watch out you could end up sending messages to a nil property. The most basic form of lazy instantiation (using literals) looks like this:

Example 1: Basic Lazy instantiation.

- (NSMutableDictionary *) dictionary {
    if (!_dictionary) {
        _dictionary = @{}.mutableCopy;
    }
    return _dictionary;
}

If you have a lot of properties, you could end up doing this a lot. An alternative is to use the long form ternary operator to simply things:

Example 2: Lazy instantiation with the long form ternary operator

- (NSMutableDictionary *) dictionary {
    _dictionary  = _dictionary ? _dictionary : @{}.mutableCopy;
    return _dictionary;
}

This is cool, but a bit repetitive. Combining the the Example 2 init style and the short form ternary operator we could simply this expression to:

Example 2: Nice.

- (NSMutableDictionary *) dictionary {
    return (_dictionary = _dictionary ?: @{}.mutableCopy);
}
  • Abstract Away *

The final step is to abstract this pattern away with a C Macro. I call mine WSM_LAZY.

#define WSM_LAZY(object, assignment) (object = object ?: assignment)


- (NSMutableDictionary *) dictionary {
    return  WSM_LAZY(_dictionary, @{}.mutableCopy);
}

Cool, this is useful when doing standard lazy instantiation, but what about more complex initializations? This is where we can take advantage of compound statements, which are a rarely used GNU C extension. Compound statements look like blocks, but implicitly return the last object evaluated, ala Ruby. The below code snippet says:

  • If _dictionary is initialized, return to me the _dictionary instance variable.

  • If not use this compound statement to create an initial value, set it to _dictionary and then return it’s value.

    - (NSMutableDictionary *) dictionary {
        return WSM_LAZY(_dictionary, ({
            NSMutableDictionary *initDictionary = @{}.mutableCopy;
            initDictionary[@"starterKey"] = @"starterObject";
            NSLog(@"Lazy instantiated dictionary: %@", initDictionary);
            initDictionary;
        }));
    }