Dictionary Fictionary

See update below: it’s more complicated than I thought.

Over the last, what, decade or so? maybe more? people have been using the word “literally” to mean…not literally.

“Literally a monster.”

“Literally the worst thing ever.”

They don’t mean literally literally. They’re using it because it feels right, regardless of its actual definition. It can lead to some amusing results.

I’ve seen something similar in Objective-C.1

When array, dictionary, and number literals and object subscripting were introduced in Objective-C in 2012, people really liked them. Me too! They were literally mostly just compiler syntactic sugar, but they derived their usefulness in part from being vastly more compact and readable.

Because the new syntax is so nifty, people want to use it everywhere.

NSMutableDictionary *foo = @{}.mutableCopy;

instead of

NSMutableDictionary *foo = [NSMutableDictionary new];

for example. Are they equivalent? Well, this is what the first sample is generating:

NSMutableDictionary *foo = [NSDictionary new].mutableCopy;

Making two dictionary instances, only to throw one away immediately, just because you want to use shiny syntax, doesn’t seem like the best idea to me.

Array and dictionary literals derive the rest of their useful from not having quite the same rules as the old syntax.

Unlike for the old creation APIs, now nil is no longer used as the ending sentinel for the list of input values. So because they could, the designers of the new syntax disallowed it completely.

The amusing part? Recently, I had a coworker who swore you couldn’t set a dictionary value to nil, like this:

foo[@"key"] = nil;

What he was doing was confusing the new literal syntax rules with plain old NSDictionary APIs. It’s an easy mistake to make. The only reason I didn’t is because I have years and years of experience with the old APIs, which didn’t have the same hangups about nil.

So I could compartmentalize the new nil rules to just the new literal syntax, where they belong.

I literally needed to run example code for him to convince him that the above line wouldn’t assert.

Update: Looks like I was right for the wrong reasons.

Instead of it always working the way I thought, the current behavior is a recent addition. See Foundation Release Notes for OS X v10.11 and iOS 9, specifically this part:

NSMutableDictionary subscript syntax change
In OS X 10.11 and iOS 9, NSMutableDictionary now allows you to use its subscript syntax to assign nil for a key. Like Swift’s Dictionary type, doing so will remove an existing object for that key from the dictionary. In effect, the following lines of code are now equivalent:

[dictionary removeObjectForKey:@"Key"];
dictionary[@"Key"] = nil;

These new semantics exist only when building with the OS X 10.11 or iOS 9 SDKs. If your application’s deployment target is earlier operating system, then runtime support will be implicitly linked into your binary by Xcode to ensure the behavior works on any targeted operation system.

My mistake was assuming the subscript setter syntax was an unmodified usage of NSMutableDictionary’s setObject:forKey:, which in my recollection always allowed nil, but of course there’s no reason it would have to be.

And now, with nullability annotations, setObject:forKey: no longer allows nil at all! (Plus, on reflection, I think I was wrong about it allowing nil in the past.)

Thanks to Jordan Rose and others for the correction.

1. Somebody get me a storyboard, cuz I just made a killer segue! ↩︎