Vincent Gable’s Blog

July 8, 2010

NSDictionary Copies It’s Keys

Filed under: Bug Bite,Cocoa,iPhone,MacOSX,Objective-C,Programming | , , ,
― Vincent Gable on July 8, 2010

An NSDictionary will retain it’s objects, and copy it’s keys.

Here are some effects this has had on code I’ve worked on.

  • Sometimes you get the same object you put in, sometimes not.
    Immutable objects are optimized to return themselves as a copy. (But with some exceptions!). So the following code:

    	NSDictionary *d = [NSDictionary dictionaryWithObject:@"object" forKey:originalKey];
    	for(id aKey in d)
    		if(aKey == originalKey)
    			NSLog(@"Found the original key!");

    Might print “Found the original key!”, and might not, depending on how [originalKey copy] is implemented. For this reason, never use pointer-equality when comparing keys.

  • Mutable objects make bad keys. If x is a mutable NSObject, [x copy] is an immutable copy of x, at that point in time. Any changes to x are not reflected in the copy. For example,
    	[dict setObject:x forKey:key];
    	//...code that changes key, but not dict
    	assert([[dict objectForKey:key] isEqual:x]); //fails!

    Because the copy is an immutable object, it will blow up if you try to mutate it.

    	NSMutableString *key = //something...
    	[dict setObject:x forKey:key];
    	for(NSMutableString *aKey in dict)
    		[aKey appendString:@"2"]; //Error, aKey isn't mutable, even though key is!
  • View objects make bad keys. Views have state related to the screen: their frame, position in the view hierarchy, animation layers, etc. When you copy a view object, the copy won’t (always) be isEqual: to the original, because it’s not on the screen in exactly the same way.
  • Your classes must support NSCopying to be used as a key in an NSDictionary, you can’t just implement -hash and -isEqual: in your custom classes.

Of course, this isn’t a complete list of every way key-copying can trip you up. But if you understand what copy means in Cocoa, and remember how NSDictionary works, you’ll be able to avoid or quickly solve any issues.

How to Document Such Behavior Better Than Apple Did

Given what we know about NSDictionary, what’s wrong with the following snippit from NSDictionary.h?

@interface NSMutableDictionary : NSDictionary
- (void)setObject:(id)anObject forKey:(id)aKey;

Answer: aKey needs to implement NSCopying, so it should be of type (id<NSCopying>) instead of type (id). That way, the header is self-documenting, and, if like most smart programmers, you’re using autocomplete to type out Cocoa’s long method names, the auto-completed template will be self-documenting too.

August 20, 2008

Hardwired Strings

With every programming language/development environment I know of, you have to do extra work to make a string localizable. For example,
errorMessage = NSLocalizedString(@"This is hard!",@"");
not simply,
errorMessage = @"This is hard!";

And it’s not just extra work, it’s a ridiculous amount of work. E.g. to create an Objective-C string, you just put an @ in front of a standard C string. But to create a localized string, you have to surround your string in ()‘s, and prefix it with a seventeen character mixed-case function-name, and finally add an empty “note” for the translator. (Rarely, you may want to add a note for the translator, and it’s good that you can, but you should not be forced to write something when it is not necessary.)

There isn’t really an excuse for this; it’s just how languages have lazily evolved.

Non-localizable (“hardcoded”) strings are a huge issue, even though they are trivially simple for programmers to fix. The problem is that translators do not work with source code, they work with resource files. So when they find a “hardwired” string that must be changed in code, they tell a programmer to fix it. And that communication is slow and costly. Especially when you consider that good localizers are probably native to the place they are localizing for — meaning that having a real-time conversation involves at least one party staying up past 3AM.

I dream of a day where "this would be localized by default"; "and"("syntactic sugar for adding optional translator notes would be there too"); and creating C"literal C-strings" would be easy.

“Unicode support libraries” are doomed to institutionalized-failure, because they will never make it easier to do the right thing. It must be so easy to create a localizable program that you wouldn’t dream of doing anything else.

Powered by WordPress