NSDictionary, along with
NSArray are the workhorse collection classes of Foundation. Unlike other standard libraries, implementation details are hidden from developers, allowing them to write simple code and trust that it will be (reasonably) performant.
However, even the best abstractions break down; their underlying assumptions overturned. In these cases, developers either venture further down the abstraction, or, if available use a more general-purpose solution.
NSDictionary, the breaking assumption was in the memory behavior when storing objects in the collection. For
NSSet, objects are a strongly referenced, as are
NSDictionary values. Keys, on the other hand, are copied by
NSDictionary. If a developer wanted to store a weak value, or use a non-
<NSCopying>-conforming object as a key, they could be clever and use `NSValue +valueWithNonretainedObject. Or, as of iOS 6 (and as far back as Mac OS X 10.5), they could use
NSMapTable, the more general-case counterparts to
So without further ado, here's everything you need to know about two of the more obscure members of Foundation's collections classes:
NSHashTable is a general-purpose analogue of
NSSet. Contrasted with the behavior of
NSHashTable has the following characteristics:
strongreferences to members, which are tested for hashing and equality using the methods
NSHashTableis mutable, without an immutable counterpart.
weakreferences to its members.
copymembers on input.
NSHashTablecan contain arbitrary pointers, and use pointer identity for equality and hashing checks.
NSHashTable *hashTable = [NSHashTable hashTableWithOptions:NSPointerFunctionsCopyIn]; [hashTable addObject:@"foo"]; [hashTable addObject:@"bar"]; [hashTable addObject:@42]; [hashTable removeObject:@"bar"]; NSLog(@"Members: %@", [hashTable allObjects]);
NSHashTable objects are initialized with an option for any of the following behaviors. Deprecated enum values are due to
NSHashTable being ported from Garbage-Collected Mac OS X to ARC-ified iOS. Other values are aliased to options defined by NSPointerFunctions, which will be covered next week on NSHipster.
NSHashTableStrongMemory: Equal to
NSPointerFunctionsStrongMemory. This is default behavior, equivalent to
NSHashTableWeakMemory: Equal to
NSPointerFunctionsWeakMemory. Uses weak read and write barriers. Using
NSPointerFunctionsWeakMemoryobject references will turn to
NULLon last release.
NSHashTableZeroingWeakMemory: This option has been deprecated. Instead use the
NSHashTableCopyIn: Use the memory acquire function to allocate and copy items on input (see
NSPointerFunction -acquireFunction). Equal to
NSHashTableObjectPointerPersonality: Use shifted pointer for the hash value and direct comparison to determine equality; use the description method for a description. Equal to
NSMapTable is a general-purpose analogue of
NSDictionary. Contrasted with the behavior of
NSMapTable has the following characteristics:
NSMutableDictionarycopies keys, and holds strong references to values.
NSMapTableis mutable, without an immutable counterpart.
NSMapTablecan hold keys and values with
weakreferences, in such a way that entries are removed when either the key or value is deallocated.
copyits values on input.
NSMapTablecan contain arbitrary pointers, and use pointer identity for equality and hashing checks.
Instances where one might use
NSMapTable include non-copyable keys and storing weak references to keyed delegates or another kind of weak object.
id delegate = ...; NSMapTable *mapTable = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableWeakMemory]; [mapTable setObject:delegate forKey:@"foo"]; NSLog(@"Keys: %@", [[mapTable keyEnumerator] allObjects]);
NSMapTable objects are initialized with options specifying behavior for both keys and values, using the following enum values:
NSMapTableStrongMemory: Specifies a strong reference from the map table to its contents.
NSMapTableWeakMemory: Uses weak read and write barriers appropriate for ARC or GC. Using NSPointerFunctionsWeakMemory object references will turn to NULL on last release. Equal to
NSHashTableZeroingWeakMemory: This option has been superseded by the
NSMapTableCopyInUse the memory acquire function to allocate and copy items on input (see acquireFunction (see
NSPointerFunction -acquireFunction). Equal to NSPointerFunctionsCopyIn.
NSMapTableObjectPointerPersonality: Use shifted pointer hash and direct equality, object description. Equal to
After looking at a few code examples, a clever NSHipster may have thought "why aren't we using object subscripting?". A particularly enterprising NSHipster may even have gotten a few lines of code into implementing a subscripting category for
So why doesn't
NSMapTable implement subscripting? Take a look at these method signatures:
- (id)objectForKeyedSubscript:(id <NSCopying>)key; - (void)setObject:(id)obj forKeyedSubscript:(id <NSCopying>)key;
Notice that the
key argument is of type
<NSCopying>. This is great for
NSMutableDictionary, but we can't make that assumption for
NSMapTable. And so we arrive at an impasse: with an
id <NSCopying> type, we can't implement for
NSMapTable, however if object subscripting methods were to drop the
<NSCopying> constraint, then we would miss out on the compiler check in
So it goes. Honestly, in a situation where
NSMapTable is merited, syntactic sugar is probably the least of one's concerns.
As always, it's important to remember that programming is not about being clever: always approach a problem from the highest viable level of abstraction.
NSDictionary are great classes. For 99% of problems, they are undoubtedly the correct tool for the job. If, however, your problem has any of the particular memory management constraints described above, then
NSMapTable may be worth a look.