ecbtln
ecbtln

Reputation: 2746

NSMutableDictionary that retains its keys

I'm trying to figure out how to create an NSMutableDictionary that retains instead of copies its keys. I have implemented -(NSUInteger)hash and -(id)isEqual: for my desired keys, I am just having trouble figuring out which options to specify in the callbacks.

CFDictionaryKeyCallBacks    keyCallbacks    = { 0, NULL, NULL, CFCopyDescription, CFEqual, NULL };


self.commonParents = (NSMutableDictionary*)CFBridgingRelease(CFDictionaryCreateMutable(nil, 0, &keyCallbacks, &kCFTypeDictionaryValueCallBacks));

The above code works correctly in ARC for using weak references to keys, but what if I want strong references? What should the key callbacks look like?

Upvotes: 2

Views: 1123

Answers (4)

Kelan
Kelan

Reputation: 2276

I think the best answer is buried in comment, so I'll highlight it here: The simplest approach is to use a +[NSMapTable strongToStrongObjectsMapTable] (or maybe one of the variants with weak references).

Upvotes: 1

Dave DeLong
Dave DeLong

Reputation: 243156

tl;dr:

Create a CFDictionaryRef with the provided default callback functions. It'll do what you want. Just don't call it an NSDictionary.


Yes, you can create a CFDictionaryRef that retains its keys and does not copy them. This is, in fact, the default behavior of a CFDictionaryRef.

The documentation for CFDictionaryCreateMutable() says:

If the dictionary will contain only CFType objects, then pass a pointer to kCFTypeDictionaryKeyCallBacks as this parameter to use the default callback functions.

(So if you're only going to be putting normal Objective-C objects into the array and not random things like void * pointers or whatever, this is what you want)

And the documentation for kCFTypeDictionaryKeyCallBacks says:

Predefined CFDictionaryKeyCallBacks structure containing a set of callbacks appropriate for use when the keys of a CFDictionary are all CFType-derived objects. The retain callback is CFRetain, the release callback is CFRelease, the copy callback is CFCopyDescription, the equal callback is CFEqual. Therefore, if you use a pointer to this constant when creating the dictionary, keys are automatically retained when added to the collection, and released when removed from the collection.

Note that the retain callback is CFRetain() and not something like CFCopyObject (which doesn't actually exist).

In fact, Core Foundation doesn't have a canonical way to "copy any object", which is why functions like CFStringCreateCopy, CFArrayCreateCopy, CGPathCreateCopy, etc exist.

So, what you can do is create your dictionary like this:

CFDictionaryRef dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

And you now have a dictionary that retains its keys and does not copy them.

I'm going to put the following bit in big letters so that you grok what I'm about to say:

This dictionary you've created is not an NSDictionary.

Yes, NSDictionary and CFDictionaryRef are toll-free bridged. But casting this CFDictionaryRef to an NSDictionary would be an abuse of that bridging, because of this line in the NSDictionary documentation:

...a key can be any object (provided that it conforms to the NSCopying protocol—see below)

Similarly, the documentation for -[NSMutableDictionary setObject:forKey:] explicitly says:

The key is copied (using copyWithZone:; keys must conform to the NSCopying protocol).

The keys in your dictionary don't have to conform to <NSCopying> and are not copied using -copyWithZone:, which means your dictionary is NOT an NSDictionary (or NSMutableDictionary). Any time you see NSDictionary used in code, you should be providing a key-value map where the keys are copied (not retained). That is the API contract. To do anything else could result in undefined behavior.

(The fact that some objects override -copy to return [self retain] is an implementation detail and is not relevant to this discussion on "what is an NSDictionary".)

Upvotes: 5

Vincent Bernier
Vincent Bernier

Reputation: 8664

I think there is 2 possibles solutions that could be achieved using plain old NSMutableDictionary. They are not as elegant as NSMapTable would be.
You state that each of your Key have a uniqueID, so I assume that this Value won't change over time.

Option 1 :
Use the uniqueID of your actual key to be the key of an NSMutableDictionary that would store NSArray of @[key, value] so the whole structure look like this
@{ key1.uniqueID : @[key1, value1], key2.uniqueID : @[key2 : value2] }

Option 2 :
Make a subclass of NSObject that is a wrapper around option 1. Or any variation on option 1.

Those are only valid if uniqueID never change

Upvotes: 0

Ramy Al Zuhouri
Ramy Al Zuhouri

Reputation: 21966

My suggest is that instead of doing this you subclass NSString or whatever class you're using as key, and override the copy method in a way that it returns the string retained, instead of a copied string.

Upvotes: 0

Related Questions