There may arise a situation where you absolutely can’t do something without either doing ugly hacks with overriding -release
(which you should never, ever do), or using non-Objective-C constructs such as C++’s std::map
(shudder), or rolling out your own key-value storage data structure (evil NIH syndrome strikes again).
The Reason
The only valid reason I can think of for doing this is to avoid a cyclic reference. For example, an object must be stored in a dictionary, but should be automatically removed from it upon being -dealloc
‘ed. This is exactly what I’m doing in a game I’m slowly working on in order to cache OpenGL textures. I tried hacking this by overriding -release
and monitoring -retainCount. Any Objective-C developer worth the name will know that’s a pretty dumb thing to do: -retainCount
is a pretty shaky thing to depend on, due to various compiler optimizations, and overriding -release
can only cause issues. (Yes, that includes overriding -release
to force yourself and your teammates never to destroy a singleton. I actually saw that being done in production code. Pretty nasty stuff.)
Pretty much invalid reasons are:
- storing non-Objective-C pointers or data types. If you’re making use of
NSMutableDictionary
, you’re probably making use of Objective-C and Foundation. So make use of the reference counting mechanism built into the framework by wrapping the non-Objective-C data type into something that can be swallowed by the framework nice and easy. Create a thin wrapper around this data by subclassingNSObject
- not wanting to deal with the reference counting system. Oh, so you’re one of those people who don’t like to use
-retain
/-release
/-autorelease
? You find them abhorrent? Go and cry to your mommy; the reference counting system is one of the most powerful mechanisms enabled by Objective-C and provided by Foundation. Shunning it is no good.
Oh, and if you’re one of the ARC-loving pansies developers, sorry; I have no idea what effect this’ll have on your funny-colored little world. Because we’re about to dive into the mean world of Core Foundation.
This also, sadly, means I have no idea how this’ll work with GNUstep.
The Explanation
So you may have heard that Core Foundation equivalents of Foundation classes are “toll-free bridged”. What does this mean?
This means that if you create a CFArray
, you can use the resulting pointer as an NSArray
, and vice versa. This is pretty handy if you’re writing code that interacts with Mac OS X’s kernel. When writing something that talks to Bluetooth subsystem (say, a new Bluetooth service), you will use C functions that accept and return CFDictionary
instances. Oh, sir, yes they do.
So to clean up your code of all those nasty CFDictionary*()
function calls, and make it look all nice and Objective-C-ish, what can you do? You just pass the resulting CFDictionary
pointer as the first thing in the brackets (you know, where you usually put an Objective-C message target?) and you use plain old Foundation message sends to do operations with the dictionary. To get rid of the warning, you can cast it either prior to the message send or in-line when performing the send.
CFDictionaryRef dict; // same as CFDictionary * // . . . initialize it here . . . [((NSDictionary*)dict) valueForKey:@"someKey"]; // ...or alternatively: NSDictionary * theDict = (NSDictionary*)dict; [dict valueForKey:@"someKey"];
And you can also do the opposite thing! You can create an NSDictionary
and pass it off as a CFDictionary
.
NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:@"value", @"key", nil]; // . . . use it here . . . // now we'd have to do [dict release]. // or, we could have autoreleased the object right after // initializing it. // but let's be fancy. CFDictionaryRef cfDict = (CFDictionaryRef)dict; CFRelease(cfDict);
The Solution
So how do we actually create a NSMutableDictionary
whose objects won’t be retained nor released?
It turns out to be wonderfully simple. You see, CFDictionaryCreateMutable()
is a C function. And C doesn’t have a concept of reference counting built deep down into its core. So when you create a dictionary for use with C code, in a C-only program, you probably don’t want the dictionary to try to send messages to pointers which are not really Objective-C objects.
And as we have demonstrated each CFDictionary
is actually an NSDictionary
.
If you are using a C function, it’s a good idea to actually default to C behavior: no retaining and no releasing. It might also be a good idea to allow one to use a third-party reference counting mechanism?
That’s exactly what was done here. When calling CFDictionaryCreateMutable()
, you feed it an allocator which can be used to allocate memory instead of the default one, the default capacity (just like -initWithCapacity:
), and two pointers which describe just how the dictionary should behave when retaining, releasing, describing, copying, hashing and comparing values and keys.
First thing I did, and that seems to work quite well, is just pass NULL
for the last two pointers. That is, it works quite well when your keys are constant strings which won’t be released that easily. I haven’t experienced a crash even when they aren’t, but let’s not risk it.
So let’s see.
NSMutableDictionary * ourDictionary = (NSMutableDictionary*)CFDictionaryCreateMutable(nil, 0, NULL, NULL);
Good, but let’s improve it by passing a pointer to a default structure for copying and releasing keys. Note that NSMutableDictionary
also copies its keys. Exploring why it does so should be an exercise for the reader.
NSMutableDictionary * ourDictionary = (NSMutableDictionary*)CFDictionaryCreateMutable(nil, 0, &kCFCopyStringDictionaryKeyCallBacks, NULL);
Now our keys are copied and released where appropriate, while the values are left untouched.
Optionally, explore using kCFTypeDictionaryKeyCallBacks
in situations where your keys may be other CFType
-derived objects. (That is, not just CFString
s/NSString
s.) Don’t use this if there is even a remote chance of your key being a mutable object.
–
via blog.vucica.net
I've tryed your suggestion, but xcode complains about an error:
Cast of C pointer type "CfMutableDictionaryRef" to Objective-C pointer type "NSMutableDictionary *" requires a bridged cast
The resulting change accepting the xcode suggestion is the following:
NSMutableDictionary *nonRetained = (NSMutableDictionary*) CFBridgingRelease(CFDictionaryCreateMutable(nil, 0, &kCFCopyStringDictionaryKeyCallBacks, NULL));
Quite possibly. I have never done anything with ARC (automatic reference counting), so I wouldn't know what fixes are needed.
But, considering how hacky this is, and how much it depends on PRECISE, controllable behavior of the runtime, Foundation and Core Foundation, I would HIGHLY advise against using this hack in an ARC-enabled module. Any classes of objects that do caching based on this hack should also NOT be stored within ARC-enabled modules.
Because ARC is too magic. Less so than GC, but still, it's magic.
Thanks, this is pretty neat