Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merge NSDictionaries - NSUnknownKeyException

I'm trying to merge two NSDictionaries:

NSDictionary *areaAttributes = [[area entity] attributesByName];
NSDictionary *gpsAttributes = [[gps entity] attributesByName];
NSMutableDictionary *combinedAttributes = [areaAttributes mutableCopy];
[combinedAttributes addEntriesFromDictionary:gpsAttributes];

But get the following error:

Terminating app due to uncaught exception 'NSUnknownKeyException', reason: 'The key 'latitude' is not defined for this NSKnownKeysDictionary'

latitude is a key in gpsAttributes

like image 491
OdieO Avatar asked Jan 15 '13 05:01

OdieO


1 Answers

The problem here is that you're not dealing with ordinary dictionaries, but with NSKnownKeysDictionary, which is an undocumented type that Apple uses in various parts of Cocoa.

If you dump the ObjC class info from the SDK—or, if that sounds too scary, read someone else's version—you can see that this is actually a subclass of NSMutableDictionary, not just NSDictionary, so it's already mutable, so your mutableCopy isn't necessary.

More importantly, if you read up on how the NSCopying and NSMutableCopying protocols are defined, there's nothing that requires mutableCopy to return you something that's mutably-compatible with its base class; only that it be mutable, and immutably-compatible with its base class. Which is annoying.

The answer here is to generate a new mutable dictionary, and copy the keys from both NSKnownKeysDictionary objects into that:

NSDictionary *areaAttributes = [[area entity] attributesByName];
NSDictionary *gpsAttributes = [[gps entity] attributesByName];
NSMutableDictionary *combinedAttributes = [NSMutableDictionary dictionaryWithCapacity:20];
[combinedAttributes addEntriesFromDictionary:areaAttributes];
[combinedAttributes addEntriesFromDictionary:gpsAttributes];

(Obviously, if you're worried about performance, you either want to tweak that 20, or get the count of keys from each dictionary and pass in the total. But usually, it doesn't matter.)

You may be wondering why Apple did this. But if you think about it, it's a handy thing. You've got an object that can be used like a C struct within the guts of some library, but externally looks just like an NSDictionary. (A similar purpose to, e.g., namedtuple in Python.) The methods that return them are all designed to return NSDictionary, not NSMutableDictionary, so nobody's going to know they have a restricted set of keys. Except, of course, if they call mutableCopy.

You may also be wondering how you can know about this in advance. Well, you really can't. Either you log the types of the objects, you write an exception handler and log the exception, or you let it crash and find the exception in the crash dump. Or, alternatively, you can be defensive and assume that any dictionary can give you a useless mutableCopy and shallow-copy it into a dictionary of a type you control.

like image 82
abarnert Avatar answered Nov 13 '22 17:11

abarnert