Welcome back to another edition of Friday Q&A. For this week's post, I'm going to talk about three somewhat obscure collections classes that were introduced to Cocoa in 10.5:NSPointerArray,NSHashTable, andNSMapTable, a topic suggested by Phil Holland.
Introduction
These classes were introduced in 10.5 and it appears that their main purpose was to add some useful capabilities for the newly-introduced Cocoa garbage collection support. However, they add a lot of other useful capabilities beyond that as well.
Each of these classes is the counterpart of a more traditional Foundation collection class.NSPointerArray is the counterpart ofNSArray,NSHashTable is the counterpart ofNSSet, andNSMapTable is the counterpart ofNSDictionary. They are not identical, but share a lot of behaviors, and are the same basic kind of container.
One nice feature of Cocoa garbage collection (and many other collectors) is zeroing weak references. These are references to an object which don't keep that object alive. Instead, they point to the object while it's alive, but if and when it gets garbage collected, the collector zeroes out the reference. This can be really useful for object caches, parent-child object relationships, automatic deregistration of observers and others.
Individual weak references are easy to use: simply prepend__weak to an object pointer variable's type and that variable becomes a weak reference. (Note: this does not work for local variables.) The standard collections all hold strong references to their contents. One of the reasons for these new collection classes was that they can be configured to hold weak references to their contents, greatly expanding the uses for weak references.
These classes are also much more flexible in general. They can holdNULL values, they can be configured to hold opaque non-object pointers, plain integers, or even pointers to memory with custom comparison/destruction operations.
NSPointerArray
This class is a lot like anNSMutableArray, but with theid objects replaced withvoid *, and many fewer convenience functions.
The major difference fromNSMutableArray is in how you create a newNSPointerArray. The class has two initializers:
-initWithOptions:(NSPointerFunctionsOptions)options;-initWithPointerFunctions:(NSPointerFunctions*)functions;
NSPointerArray treats its contents. You can use anNSPointerArray any time you want anNSArray that points to special kinds of objects or otherwise needs special treatment.NSPointerFunctionsNSPointerFunctions is a class whose main purpose is to hold a bunch of function pointers. There are function pointers for hashing, equality, memory management, and more. There are also two flags you can set to have it use different garbage collection read/write barriers. By stuffing function pointers into this class, you can specify how everything works.
Let's build an example. We'll create an NSPointerFunctions object that deals in pointers to integers. Not too useful, but a good exercise.
The first thing to do is to define all of the various functions that will be needed. Hashing is easy, just return the integer that the pointer points to:
staticNSUIntegerHash(constvoid*item,NSUInteger(*size)(constvoid*item)){return*(constint*)item;}
staticBOOLIsEqual(constvoid*item1,constvoid*item2,NSUInteger(*size)(constvoid*item)){return*(constint*)item1==*(constint*)item2;}
size parameter to both functions. If necessary, this will get the size of the pointed-to object. We already know the size, so there's no need to use it. Since it's unnecessary, I won't provide a size function to theNSPointerFunctions object either.For the description, we just return a simple string using the integer obtained from dereferencing the pointer:
staticNSString*Description(constvoid*item){return[NSStringstringWithFormat:@"%d",*(constint*)item];}
malloc andfree. I decided to just have relinquish always free the item, and acquire return a copy. The relinquish function is simple:staticvoidRelinquish(constvoid*item,NSUInteger(*size)(constvoid*item)){free((void*)item);}
mallocs some new memory, copies the value, and returns the new pointer:staticvoid*Acquire(constvoid*src,NSUInteger(*size)(constvoid*item),BOOLshouldCopy){int*newPtr=malloc(sizeof(int));*newPtr=*(constint*)src;returnnewPtr;}
NSPointerFunctions, and a newNSPointerArray from it:NSPointerFunctions*functions=[[NSPointerFunctionsalloc]init];[functionssetHashFunction:Hash];[functionssetIsEqualFunction:IsEqual];[functionssetDescriptionFunction:Description];[functionssetRelinquishFunction:Relinquish];[functionssetAcquireFunction:Acquire];NSPointerArray*array=[NSPointerArraypointerArrayWithPointerFunctions:functions];intone=1,two=2,three=3;[arrayaddPointer:&one;];[arrayaddPointer:&two;];[arrayaddPointer:&three;];
NSPointerOptions
It may work, but that was a gigantic hassle. Fortunately, Apple has provided a bunch of pre-bakedNSPointerFunctions to work with. These can be accessed by usingNSPointerFunctionsOptions constants.
NSPointerFunctionsOptions is composed of three parts. There are memory options, personalities, and flags.
Memory options determine memory management. UsingNSPointerFunctionsStrongMemory will get you the behavior of the more standard Foundation collection classes: a strong reference for garbage collection, and a retained reference for manual memory management.NSPointerFunctionsZeroingWeakMemory will get you zeroing weak references under garbage collection and, I believe, a non-retained reference under manual memory management.There are also options formalloc/free management, for Mach virtual memory, and for completely ignoring memory management.
Personalities determine hashing and equality.NSPointerFunctionsObjectPersonality provides the standard Foundation behavior of usinghash andisEqual:. You can also useNSPointerFunctionsObjectPointerPersonality, which treats the contents as objects, but uses direct pointer value comparison; this is useful if you need a collection to work with object identity rather than value.NSPointerFunctionsIntegerPersonality allows storing pointer-sized integers directly in the container. Note that unlike the toy example above, this doesn't deal withpointers to integers, but rather integers directly, like storing(void *)42. This is useful if you need a collection that stores integers and don't want the code and runtime overhead of packing the integers into objects.
Apple has only given us one flag at the moment:NSPointerFunctionsCopyIn. When set, this flag will cause newly inserted pointers to be copied rather than simply retained. What exactly this means will depend on the personality set, but in the case of object personalities, it will useNSCopying.
Some examples:
NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPersonality | NSPointerFunctionsCopyIn.NSPointerFunctionsZeroingWeakMemory | NSPointerFunctionsObjectPointerPersonality.malloc memory, copied when inserted:NSPointerFunctionsMallocMemory | NSPointerFunctionsCStringPersonality | NSPointerFunctionsCopyIn.Now that you know how all of those work, let's look at the remaining two classes.
NSHashTableNSHashTable is the rough equivalent ofNSMutableSet. It's an unordered collection of objects, using hashing to allow for fast access. Again, the basic functionality is the same, but without some of the more advanced methods. And again, it has two initializers which take pointer functions and options.
There is one extra factory method onNSHashTable:
+(id)hashTableWithWeakObjects;
LikeNSPointerArray, you can useNSHashTable any time you want anNSSet but with some different capabilities in terms of how it treats its contents.
NSMapTableNSMapTable is the final class of the three, and is the rough equivalent ofNSDictionary. It's an unordered collection of key/object pairs, with fast access to the objects by looking them up through their keys. Like the others, it has the standard two initializers to tell it how to act.
NSMapTable also has four extra factory methods:
+(id)mapTableWithStrongToStrongObjects;+(id)mapTableWithWeakToStrongObjects;+(id)mapTableWithStrongToWeakObjects;+(id)mapTableWithWeakToWeakObjects;
As with the others, you can useNSMapTable in any case where you'd normally want anNSDictionary but need a little extra flexibility.
In particular,[NSMapTable mapTableWithStrongToStrongObjects will give you an object which behaves much like anNSDictionary but which doesn't copy its keys. This is useful in all kinds of situtaions, and can save a lot of headache.
Comparison with CoreFoundation
Those of you who are intimately familiar with CoreFoundation collections probably nodded a lot at theNSPointerFunctions stuff. CoreFoundation collections such asCFArray require nearly identical functions to determine how they treat their contents. Given this, what are the advantages of each?
The new Cocoa classes are largely useful because they're vastly more convenient to set up. CoreFoundation has no equivalent for a lot of the built-inNSPointerOptions functionality, which would require you to build them all yourself. There is not, as far as I know, any way to do zeroing weak references with CoreFoundation collections. Toll-free bridging is also inconsistent when it comes to custom behavior: you can build aCFDictionary with callbacks that don't copy their keys, but using[customDictionary setObject: obj forKey: key] will still copy the key even though you expressly told it not to! (For any Apple employees reading this, I've filed this asbug #4350677, and it was returned as "behaves correctly".)
Custom CoreFoundation collections can be better due to toll-free bridging, which allows you to treat them as standardNSArrays and so forth. However, you must be careful when passing these to code you don't own, as they may make assumptions about the behavior of the collection which your custom callbacks don't respect. And as noted above, the behavior of custom callbacks with toll-free bridging is inconsistent, so watch out!
All in all, each one has its place. In pure Cocoa code, the new Cocoa classes are generally more convenient and can be more powerful.
Conclusion
The new collection classes added to Cocoa in 10.5 are a powerful, flexible addition to Foundation. They easily allow useful behaviors like storing weak references and raw integers, or just creating an object map that doesn't copy its keys.
That's it for this Friday Q&A. Come back in another two weeks for the next one, unless I decide that working it in around WWDC is too much, in which case I'll shoot for the week after. And if you're going to WWDC too, say hi!
Add your thoughts, post a comment:
Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.