NSManagedObject as NSDictionary key?
-
19-09-2019 - |
Question
In my app, I have a NSDictionary
whose keys should be instances of a subclass of NSManagedObject
.
The problem, however, is that NSManagedObject
does not implement the NSCopying
protocol which means that no Core Data objects / instances of NSManagedObject
can be used as dictionary keys even though the -[hash]
method works fine for them.
Was should I do?
Solution
There are four options:
- Use a different object as the dictionary key instead, and lookup from that.
[object objectID]
or+[NSValue valueWithNonretainedObject:]
seem the most obvious - Use
CFDictionaryCreateMutable()
to create a dictionary with retained keys, rather than copied, instead, and then callCFDictionarySetValue()
to store the objects - On OS X or iOS6+,
[NSMapTable mapTableWithStrongToStrongObjects]
gives you a purely Objective-C equivalent toCFMutableDictionary
- Implement
NSCopying
for your managed object subclass, such that it returns self (with a bumped reference count if you're not using ARC)
Notes
+valueWithNonretainedObject:
is pretty dangerous, since it's possible to be left with a dangling pointer; likely best to avoid.
Storing object IDs is fine, apart from the fact that new objects start out life with a temporary ID. That ID then changes to a permanent one when the context is saved to disk (or -obtainPermanentIDsForObjects:…
is called). Your mapping code needs to be smart enough to handle this unless it can guarantee that all incoming objects already have a permanent ID.
Implementing NSCopying
like this feels a bit icky, but should work just fine. As it happens, this is exactly the approach NSURLSessionTask
takes, I presume for dictionary friendliness.
Prior to OS X 10.8 Mountain Lion, it used to be possible to create a regular NSMutableDictionary
and then call CFDictionarySetValue()
for it. That's no longer the case though; new dictionaries now have proper copy callbacks specified down at the CF level, rather than purely being a feature of NSMutableDictionary
.
OTHER TIPS
Could you create a wrapper class, that contains a reference to the instance of NSManagedObject that you want to use as a dictionary key? You could then make this wrapper class implement NSCopying, along with a hash method (perhaps just calling the NSManagedObject's hash method), and use this wrapper as the dictionary key.
I suggest to use [[[myManagedObject objectID] URIRepresentation] absoluteString] as your key.
I had a similar problem, in which I needed to bundle several entities with additional data for each, and initially tried:
@{entity1:data1, @entity2:data2, @entity3:data3}
this didn't work for the reason above (NSCopying), so I did:
@[
@{@"entity":entity1, @"data":data1},
@{@"entity":entity2, @"data":data2},
@{@"entity":entity3, @"data":data3}
]
But this solution makes sense only if you don't need dictionary style access to these entities or are happy to iterate to find what you need. In my case this was a packaging problem. Note that if you pass these entities around the NSManagedObjectContext need to be the same to use them.