Ever seen this hard to notice bug in an iPhone / Mac project? Caused by a flaw (bug?) in Apple’s own API:
NSDictionary * map;
NSObject * key;
NSObject * value;
// NB: next line will frequently crash at runtime
// …but even if doesn’t crash, it probably won’t do
// what you’d expect it to:
[map setObject:value forKey:key];
if( value != [map objectForKey:key] ) // HA!
NSAssert( 1 == 0, @”What the **** just happened??” );
NSDictionary is one of the nastier things I’ve seen in a programming language: in an Object-Oriented Language, it’s a class that refuses to take Objects as arguments, *but pretends to*. If you attempt it, it either crashes, or it invalidates your objects, breaking contracts all over the place.
ObjC’s bizarre design
In the days pre-OOP, a “dictionary” was something that mapped:
“anything” (usually: basic datatypes – e.g. strings, integers, floats, etc).
In the days of OOP, the same thing is usually called a “map” (which is a better term) – although the terms are synonymous – and maps:
What did Apple/NextStep/Crazy-Guys-Behind ObjC do?
“STRINGS ONLY” (no objects allowed!)
“OBJECTS ONLY” (no core datatypes allowed!)
But … I can use an NSObject as key?
Yep – but Apple’s internal implementation takes a *copy* of the object, and uses that as a key – rather than using the object that you gave it. This is a common problem in OOP languages and implementations of Map – e.g. Java does the same thing.
Unfortunately, this means that you can call “setObject:forKey:”, and then “objectForKey:” will return nil *for the same key*.
In Java and other OOP languages, you are required to implement a custom “isObjectEqualToObject” method. In ObjC too – except that that method is ILLEGAL if you’re using CoreData.
And ObjC will crash too, as a bonus
I’ve never seen this in other OOP languages, but in ObjC *additionally*: if you don’t manually add
How come? AFAICT, Apple’s header file is wrong:
– (void)setObject:(id)anObject forKey:(id)aKey // You lie!
it seems the implementation of that method is:
– (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey; // the correct signature?
Net result? Code that happily compiles … will crash. ARGH!
Two reasons to beware…
…so what’s the other one?
Ah, just the one we see again and again on live projects, wasting hours and hours of time:
NSDictionary* dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
NSLog( @”dictionary = %@”, dictionary ); // but it only has one key/value pair. ?!?!?
Ah, well … that nil … what happens when object2 is nil? Oh, damn.
What about if key2 is nil? Now we’re really nasty … we’ve given it “half” of key/value pair. Nice!