Search

Objective-C Literals, part 2

Mark Dalrymple

10 min read

Mar 18, 2012

Objective-C Literals, part 2

You might want to check out Part 1 if you haven’t already seen the new NSNumber, NSArray, and NSDictionary literal syntax, and using square brackets to dig into collections.

Implications, the First

Every change to a programming language comes with implications. These are some issues that come to mind immediately, and I am sure that more will appear as more programmers of different abilities and viewpoints use the literal syntax and use the classes that support indexed and keyed access.

The literal syntax is extremely convenient. A pile of ugly code gets turned into something straightforward, almost beautiful. The downside is that this can lead to more stuff being hard-coded in source files that should really be gotten from somewhere else, like a plist file or from user defaults.

You can create some correct, but ugly code:

NSLog (@"%c", [@[ @{ @1 : @"hi"}][0][@1] UTF8String][1]);

This prints out “i”.

I showed an earlier version of this posting to Mikey Ward, Big Nerd Ranch instructor. He merely muttered “well if you truly hate yourself and those around you…”

Intentionally obnoxious code aside, this highlights one of the potential issues with the new syntax: it becomes even more convenient to chain a number of object accesses together into one line of code. In the above case, two static collections are created. The array is indexed by zero to get the dictionary, and then the dictionary is indexed by an NSNumber object. A method call is made that returns a char * pointer, which is then indexed using C array rules to get the second element.

Chains of code like that can be difficult to debug if some operation in the middle returns a nil value. Objective-C does not throw exceptions on message-sends to nil (they’re just no-ops), so suddenly the tail part of a call chain turns into a no-op. If that zero-pointer value propagates to a C-array index, you’ll crash. You can’t put a breakpoint in the middle of one of those chains to see what’s going on. This is not a new problem. Objective-C since the beginning of time lets you nest message-sends as deeply as you wish. It continued with the introduction of dot notation which makes it easier.to.chain.lots.of.stuff.to.redonkulous.depths.

It will be interesting working with newcomers to Objective-C once this hits the mainstream. Square brackets used for message sends cause enough consternation with programmers new to the language, and now there’s more layers of brackets, some have at-signs in front of them, some don’t. Sometimes the brackets dig into arrays, sometimes they dig into dictionaries. Sometimes they send messages. Sometimes they dereference pointers. I learned all this stuff piecemeal, spread out through the years, so it’s all Obvious and Wonderful to me. But it may be overwhelming for someone fresh to the platform. Then again, it might not be an issue.

Making your own indexable collections

The new collection-access syntax is a welcome change, making it very convenient to dig into collections. What’s even better is that our own classes can play in this world. There are two flavors of convenient collection access: scalar indexing which is used by NSArray, and object indexing which is used by NSDictionary.

Scalar Indexing

Two methods are involved with scalar indexing:

- (id) objectAtIndexedSubscript: (NSInteger) index;
- (void) setObject: (id) thing
         atIndexedSubscript: (NSInteger) index;

The first message is sent when you use square brackets to retrieve a value in an NSArray by index. It can be used on mutable and immutable collections. The second changes a value in a collection. Naturally it’s used for mutable collections.

Why didn’t Apple just use objectAtIndex: and replaceObjectAtIndex:withObject: to do this work? I have no idea (and have no access to the Apple-internal discussions). But the first thing that stood out to me is that the new calls take signed NSIntegers, and the existing NSArray methods take unsigned NSUIntegers. This means you could implement negative array subscripting in your own collections like I talked about last time. These new methods parallel the object-indexing methods that do the same kind of work (you’ll see these in a little bit). They may have come first, and these scalar methods came later to parallel the object-index ones. There may also have been some baggage associated with objectAtIndex: et.al. that made directly piggybacking the bracket syntax on them difficult.

A Negative-Indexed Array

Being able to index arrays from the end can be convenient. In languages like Python you can access the last element of an array with array[-1], the penultimate element with array[-2], the antepenultimate element with array[-3] (thanks to the Pittsburgh CocoaHeads for introducing me to this term) and so on.

Here is a mutable collection that supports regular and negative indexing. Under the hood it uses an NSMutableArray to actually hold the objects, but you could use any data structure you wanted to.

The interface is pretty simple. BNRNegativeArray.h looks like this:

#import <Foundation/Foundation.h>

@interface BNRNegativeArray : NSObject
- (id) objectAtIndexedSubscript: (NSInteger) index;
- (void) setObject: (id) thing  atIndexedSubscript: (NSInteger) index;
@end // BNRNegativeArray

You have to declare the actual methods to support indexed subscripting.

The implementation starts out with a class extension that declares an instance variable to hold the NSMutableArray used as the backing store, along with a simple init and a description that prints out the contents of the array. That’ll be the main way of seeing that the negative array is behaving correctly:

#import "BNRNegativeArray.h"

@interface BNRNegativeArray () {
    NSMutableArray *_backingstore;
}
@end // extension

@implementation BNRNegativeArray
- (id) init {
    if ((self = [super init])) {
        _backingstore = [NSMutableArray array];
    }
    return self;
} // init

- (NSString *) description {
    return [_backingstore description];
} // descriptioin

The implementation of the accessor is very straightforward. If the index is negative, do a little arithmetic to put the index on the positive side of zero.

- (id) objectAtIndexedSubscript: (NSInteger) index {
    if (index < 0) index = _backingstore.count + index;
    return [_backingstore objectAtIndex: index];
} // objectAtIndexedSubscript

You can do the exact same calculation in setObject:atIndexedSubscript: and call it a day. But for fun, and to make the mutating method a little more interesting, the array will use a null-fill when setting a value far off the end of the collection (where “far” is more than one index). Recall that with vanilla Cocoa, setting a value exactly one index past the end of an NSMutableArray will extend it and anything farther throws an exception. BNRNegativeArray will fill in intervening indexes with [NSNull null].

- (void) setObject: (id) thing  atIndexedSubscript: (NSInteger) index {
    if (index < 0) index = _backingstore.count + index;
    // Mutable array only allows setting the first-empty-index, like
    // -insertObject:atIndex:.  Any past that throws a range exception.
    // So let's be different and fill in intervening spaces w/ [NSNull null]
    // If you want to see @NULL, dupe rdar://10892975

    NSInteger toAdd = index - _backingstore.count;;
    for (int i = 0; i < toAdd; i++) {
        [_backingstore addObject: [NSNull null]];
    }
    if (index >= _backingstore.count) {
        [_backingstore addObject: thing];
    } else {
        [_backingstore replaceObjectAtIndex: index  withObject: thing];
    }
} // setObject atIndexedSubscript

@end // BNRNegativeArray

And that’s it. You now have an array-like collection.

Here is the collection in action:

        BNRNegativeArray *negatory = [[BNRNegativeArray alloc] init];
        negatory[0] = @"hi";     // insert
        negatory[1] = @"ohai";   // insert
        negatory[0] = @"obai";   // change
        negatory[10] = @"end!";  // null-fill and insert
        NSLog (@"negatory: %@", negatory);

This adds an object, adds a second object, changes the first object, and inserts one at the eleventh location:

negatory: (
    obai,
    ohai,
    "<null>",
    "<null>",
    "<null>",
    "<null>",
    "<null>",
    "<null>",
    "<null>",
    "<null>",
    "end!"
)

And you can index forward and backwards:

        NSLog (@"0 - %@", negatory[0]);
        NSLog (@"5 - %@", negatory[5]);
        NSLog (@"10 - %@", negatory[10]);
        NSLog (@"-1 - %@", negatory[-1]);
        NSLog (@"-10 - %@", negatory[-10]);
        NSLog (@"-11 - %@", negatory[-11]);

Prints out:

0 - obai
5 - <null>
10 - end!
-1 - end!
-10 - ohai
-11 - obai

Object Indexing

Instead of using a scalar value, you use an object to index into dictionaries. Part 1 shows strings used as keys into a dictionary, but you could use any object that obeys the dictionary key rules (can be copied, and its isEqual: and hash obey the law). As TVL pointed out in the comments last time, KVC key paths are not used by NSDictionary because this would prevent you from having a key that contained a period.

So, how is object indexing done? There are two methods similar to the scalar indexing methods:

- (id) objectForKeyedSubscript: (id) key;
- (void) setObject: (id) thing
         forKeyedSubscript: (id<NSCopying>) key;

Like array indexing, the first method is for accessing mutable and immutable collections. The second is for adding or changing values in the collection. Also like the array equivalents, these have to be declared in your header file so your collection can participate in the new syntax.

A Quasi-Key-Path Dictionary

Time for another simple sample collection, this time a collection with dictionary-like semantics. A convenient data structure is nested dictionaries holding a hierarchy of data, digging deeper from one dictionary to another to access the final value you want. You can do this with NSDictionaries by using Key-Value Coding and appropriately constructed key paths. This collection, called BNRDiggyDict, will let you construct and dig into nested dictionaries using a unix-style path, a string that @"looks/like/this". This particular path will look for a dictionary under the key “looks”, then look up “like” inside of that dictionary to get another one, then look up “this” inside of that dictionary to get the final values. Slashes are used instead of periods so that folks skimming this posting won’t think that key paths are being used.

This is what the interface looks like:

#import <Foundation /Foundation.h>

@interface BNRDiggyDict : NSObject
- (id) objectForKeyedSubscript: (id) key;
- (void) setObject: (id) thing  forKeyedSubscript: (id) key;
@end // BNRDiggyDict

The implementation mirrors BNRNegativeArray. A class extension declares an instance variable for a backing store, an NSMutableDictionary this time. There is also a description that prints out the backing store. Handy for debugging.

#import "BNRDiggyDict.h"

@interface BNRDiggyDict () {
    NSMutableDictionary *_rootDictionary;
}
@end // BNRDiggyDict

@implementation BNRDiggyDict
- (id) init {
    if ((self = [super init])) {
        _rootDictionary = [NSMutableDictionary dictionary];
    }
    return self;
} // init

- (NSString *) description {
    return [_rootDictionary description];
} // description

Both setter and getter methods need to dig through the dictionaries, so start off with a helper method. Given an array of keys made by splitting the path on the slashes, dig into the collection and return the innermost dictionary. It creates new dictionaries as it goes through. (This is kind of an annoying design decision because someone just poking around using unknown keys will create new inner dictionaries. Fixing this is left as an exercise.)

- (NSMutableDictionary *) innerDictionaryForSplitKey: (NSArray *) splitKey {
    NSMutableDictionary *interim = _rootDictionary;
    for (int i = 0; i < splitKey.count - 1; i++) {
        id key = [splitKey objectAtIndex: i];
        NSMutableDictionary *next = interim[key];
        if (next == nil) {
            next = [NSMutableDictionary dictionary];
            interim[key] = next;
        }
        interim = next;
    }
    return interim;
} // innerDictionaryForSplitKey

The inner dictionary lookup doesn’t look at the final key, also called the “leaf” key. The leaf key is handled by the keyed subscript methods.

Getting the object for the keyed subscript involves splitting the key on slashes. Because the key is an id, it’s necessary to see if it responds to componentsSeparatedByString: before calling it. Then actually split the key, get the innermost dictionary using the helper above, and then finally get the object. For giggles it uses the array syntax for the final dictionary access.

- (id) objectForKeyedSubscript: (id) key {
    if (![key respondsToSelector: @selector(componentsSeparatedByString:)]) {
        return nil;
    }
    // Use slashes rather than periods because we're not really doing KVC.
    NSArray *splitKey = [key componentsSeparatedByString: @"/"];
    NSMutableDictionary *dict = [self innerDictionaryForSplitKey: splitKey];
    id leafKey = [splitKey lastObject];
    id result = dict[leafKey];
    return result;
} // objectForKeyedSubscript

Changing an object works similarly. There is one annoyance. The key argument is an id<NSCopying>. You can’t really do much with it without compiler complaints until you cast it to something else. You can’t even call respondsToSelector: because NSCopying does not include that method. Here the key gets cast to a regular id, and then treated like it was in the object-subscript getter.

- (void) setObject: (id) thing  forKeyedSubscript: (id<NSCopying>) key {
    id idKey = (id) key;
    if (![idKey respondsToSelector: @selector(componentsSeparatedByString:)]) {
        return;
    }
    // Use slashes rather than periods since we're not really doing KVC
    NSArray *splitKey = [idKey componentsSeparatedByString: @"/"];
    NSMutableDictionary *dict = [self innerDictionaryForSplitKey: splitKey];
    id leafKey = [splitKey lastObject];
    [dict setObject: thing  forKey: leafKey];
} // setObject forKeyedSubscript

@end // BNRDiggyDict

And now the new collection in-action. First, make the collection and then add a single key-value pair:

    BNRDiggyDict *dauchs = [[BNRDiggyDict alloc] init];
    dauchs[@"i can has"] = @"cheezburger";
    NSLog (@"badger: %@", dauchs);

This prints out:

badger: {
    "i can has" = cheezburger;
}

Now set something deeper in the collection:

    dauchs[@"for/great/justice"] = @"every 'ZIG'";

This creates a hierarchy inside the collection:

badger: {
    for = {
        great = {
            justice = "every 'ZIG'";
        };
    };
    "i can has" = cheezburger;
}

You can access it as well:

    NSLog (@"TAKE OFF %@", dauchs[@"for/great/justice"]);

Prints out

TAKE OFF every 'ZIG'

Your collections aren’t limited to supporting either indexed subscripts or keyed subscripts exclusively. A collection can support both. It just needs to implement the appropriate methods.

Implications, The Second

As you can see, it’ll be possible to make your own collection classes that can create all sorts of cool semantics for the keys, like the diggy dict putting a hierarchy inside of a single collection. In a sense it’s a limited-scope version of C++ operator overloading. And one lesson from C++ is that because you can do something doesn’t necessarily mean you should in all cases. Will we see a proliferation of collection classes in our codebases, all using bracket syntax, with wildly different key semantics?

NSImage *image = imageCache[@"My Little Pony"];
// Returns the image loaded from <a href="http://bit.ly/MLPONY">http://bit.ly/MLPONY</a>
BOOL groovy = launchCache[@"My Little Pony"];
// Launches the missiles and destroys the world. Oops.

This minor issue aside, I really like the new Objective-C literal syntax, and it’s a bonus getting to participate in the new collection indexing syntax.

Mark Dalrymple

Author Big Nerd Ranch

MarkD is a long-time Unix and Mac developer, having worked at AOL, Google, and several start-ups over the years.  He’s the author of Advanced Mac OS X Programming: The Big Nerd Ranch Guide, over 100 blog posts for Big Nerd Ranch, and an occasional speaker at conferences. Believing in the power of community, he’s a co-founder of CocoaHeads, an international Mac and iPhone meetup, and runs the Pittsburgh PA chapter. In his spare time, he plays orchestral and swing band music.

Speak with a Nerd

Schedule a call today! Our team of Nerds are ready to help

Let's Talk

Related Posts

We are ready to discuss your needs.

Not applicable? Click here to schedule a call.

Stay in Touch WITH Big Nerd Ranch News