NSHashTable & NSMapTable
NSSet
and 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.
For NSSet
and 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 +value
. Or, as of iOS 6 (and as far back as OS X Leopard), they could use NSHash
or NSMap
, the more general-case counterparts to NSSet
or NSDictionary
, respectively.
So without further ado, here’s everything you need to know about two of the more obscure members of Foundation’s collection classes:
NSHash Table
NSHash
is a general-purpose analogue of NSSet
. Contrasted with the behavior of NSSet
/ NSMutable
, NSHash
has the following characteristics:
-
NSSet
/NSMutable
holdsSet strong
references to members, which are tested for hashing and equality using the methodshash
andis
.Equal: -
NSHash
is mutable, without an immutable counterpart.Table -
NSHash
can holdTable weak
references to its members. -
NSHash
canTable copy
members on input. -
NSHash
can contain arbitrary pointers, and use pointer identity for equality and hashing checks.Table
Usage
let hash Table = NSHash Table(options: .Copy In)
hash Table.add Object("foo")
hash Table.add Object("bar")
hash Table.add Object(42)
hash Table.remove Object("bar")
print("Members: \(hash Table.all Objects)")
NSHash Table *hash Table = [NSHash Table hash Table With Options:NSPointer Functions Copy In];
[hash Table add Object:@"foo"];
[hash Table add Object:@"bar"];
[hash Table add Object:@42];
[hash Table remove Object:@"bar"];
NSLog(@"Members: %@", [hash Table all Objects]);
NSHash
objects are initialized with an option for any of the following behaviors. Deprecated enum values are due to NSHash
being ported from Garbage-Collected OS X to ARC-ified iOS. Other values are aliased to options defined by NSPointerFunctions, which will be covered next week on NSHipster.
NSHash
: Equal toTable Strong Memory NSPointer
. This is the default behavior, equivalent toFunctions Strong Memory NSSet
member storage.NSHash
: Equal toTable Weak Memory NSPointer
. Uses weak read and write barriers. UsingFunctions Weak Memory NSPointer
, object references will turn toFunctions Weak Memory NULL
on last release.NSHash
: This option has been deprecated. Instead use theTable Zeroing Weak Memory NSHash
option.Table Weak Memory NSHash
: Use the memory acquire function to allocate and copy items on input (seeTable Copy In NSPointer
). Equal toFunction -acquire Function NSPointer
.Functions Copy In NSHash
: Use shifted pointer for the hash value and direct comparison to determine equality; use the description method for a description. Equal toTable Object Pointer Personality NSPointer
.Functions Object Pointer Personality
NSMap Table
NSMap
is a general-purpose analogue of NSDictionary
. Contrasted with the behavior of NSDictionary
/ NSMutable
, NSMap
has the following characteristics:
-
NSDictionary
/NSMutable
copies keys, and holds strong references to values.Dictionary -
NSMap
is mutable, without an immutable counterpart.Table -
NSMap
can hold keys and values withTable weak
references, in such a way that entries are removed when either the key or value is deallocated. -
NSMap
canTable copy
its values on input. -
NSMap
can contain arbitrary pointers, and use pointer identity for equality and hashing checks.Table
Note:
NSMap
’s focus on strong and weak references means that Swift’s prevalent value types are a no go—reference types only, please.Table
Usage
Instances where one might use NSMap
include non-copyable keys and storing weak references to keyed delegates or another kind of weak object.
let delegate: Any Object = ...
let map Table = NSMap Table(key Options: .Strong Memory, value Options: .Weak Memory)
map Table.set Object(delegate, for Key: "foo")
print("Keys: \(map Table.key Enumerator().all Objects)")
id delegate = ...;
NSMap Table *map Table = [NSMap Table map Table With Key Options:NSMap Table Strong Memory
value Options:NSMap Table Weak Memory];
[map Table set Object:delegate for Key:@"foo"];
NSLog(@"Keys: %@", [[map Table key Enumerator] all Objects]);
NSMap
objects are initialized with options specifying behavior for both keys and values, using the following enum values:
NSMap
: Specifies a strong reference from the map table to its contents.Table Strong Memory NSMap
: Uses weak read and write barriers appropriate for ARC or GC. UsingTable Weak Memory NSPointer
, object references will turn toFunctions Weak Memory NULL
on last release. Equal toNSMap
.Table Zeroing Weak Memory NSHash
: This option has been superseded by theTable Zeroing Weak Memory NSMap
option.Table Weak Memory NSMap
: Use the memory acquire function to allocate and copy items on input (see acquireFunction (seeTable Copy In NSPointer
). Equal to NSPointerFunctionsCopyIn.Function -acquire Function NSMap
: Use shifted pointer hash and direct equality, object description. Equal toTable Object Pointer Personality NSPointer
.Functions Object Pointer Personality
Subscripting
NSMap
doesn’t implement object subscripting, but it can be trivially added in a category. NSDictionary
’s NSCopying
requirement for keys belongs to NSDictionary
alone:
extension NSMap Table {
subscript(key: Any Object) -> Any Object? {
get {
return object For Key(key)
}
set {
if new Value != nil {
set Object(new Value, for Key: key)
} else {
remove Object For Key(key)
}
}
}
}
@implementation NSMap Table (NSHipster Subscripting)
- (id)object For Keyed Subscript:(id)key
{
return [self object For Key:key];
}
- (void)set Object:(id)obj for Keyed Subscript:(id)key
{
if (obj != nil) {
[self set Object:obj for Key:key];
} else {
[self remove Object For Key:key];
}
}
@end
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. NSSet
and 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 NSHash
& NSMap
may be worth a look.