Musings On Creating Immutable Collections Mutably

April 3, 2013

Sometimes we need to create an array whose length and members are not known at compile time. Oftentimes that looks something like this:

NSMutableArray *things = [NSMutableArray arrayWithCapacity:numThings];
for (int i = 0; i < numThings; i++)
{
    [things addObject: ... ];
}
// use 'things'...

If our collection is not to be changed later, and we’re feeling particularly pedantic, we might do something like this:

NSMutableArray *mutableThings = [NSMutableArray arrayWithCapacity:numThings];
for (int i = 0; i < numThings; i++)
{
    [mutableThings addObject: ... ];
}
NSArray *things = [mutableThings copy];
// use 'things'...

But mutableThings is still accessible. Best to tuck it away:

NSArray *things;
{
    NSMutableArray *mutableThings = [NSMutableArray arrayWithCapacity:numThings];
    for (int i = 0; i < numThings; i++)
    {
        [mutableThings addObject: ... ];
    }
    things = [mutableThings copy];
}
// use 'things'...

But that looks pretty unpleasant. How can we improve upon this?

One approach would be to use blocks in a simple fashion. The compiler’s inference of the return type can make the equivalent of the above a bit more graceful:

NSArray *things = ^{
    NSMutableArray *mutableThings = [NSMutableArray arrayWithCapacity:numThings];
    for (int i = 0; i < numThings; i++)
    {
        [mutableThings addObject: ... ];
    }
    return [mutableThings copy];
}();
// use 'things'...

I like this, but there’s a downside: we can’t use Step Over in the debugger to step through this code (we can still Step Into). You could also consider that an upside, depending on what you’re debugging.

We can take this a step further by creating a category on our favorite collections objects, and use it like this:

NSArray *things = [NSArray ap_arrayViaMutableArray:^(NSMutableArray *things){
    for (int i = 0; i < numThings; i++)
    {
        [things addObject: ... ];
    }
}];
// use 'things'...

A strong advantage here is that we’ve abstracted away all of the alloc, init and copy noise. Debugging may be a bit more cumbersome, since Step Into is going to step into the implementation of our category method before it gets to calling the block.