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.

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?

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.

It occurs to me just now that a [NSArray arrayWithObject:uncopyableObject] might work even better, because it (and its “copies”) will retain the object, so you’re sure that as long as the key-array is around, the object it represents is around. But I haven’t tried it, so maybe there are pitfalls in practice.

Depends; I’m not sure if two different NSArrays that contain the same object instance will evaluate as equal, or even have the same hash. I actually have no idea how NSDictionary does its internal hashing; the docs have been kept unhelpfully vague.

I haven’t read the CF-Lite source, but I’ve seen NSArray behave like it implements hash by just returning count. If that’s true, I could see using an array having bad performance in some situations (eg every key is an array with one item, so they all have the same hash). I don’t worry about it too much though because I generally assume NSDictionary does the right thing quickly enough until I’m proven otherwise. *knock on wood*.

Turns out [NSArray arrayWithObject:uncopyableObject] isn’t a “safe” key if uncopyableObject is mutable. I was using a for(…in…) loop to iterate over a dictionary that used arrays containing one UISegmentedControl object as a key. In the loop, the UISegmentedControl objects were changed. This threw an exception because the dictionary was “mutated while being enumerated.”

Also, given how isEqual: is defined for arrays, changing the objects in the array, can change it’s “equality”, and cause problems.

In my case, doing for(NSArray* arrayKey in [theDictionary allKeys]) instead of for(NSArray* arrayKey in theDictionary) “fixed” the problem, because the arrayallKeys wasn’t thrown off by what I was doing … I’m not sure that it’s safe though.