NSHashTable & NSMapTable
NSSet
和 NSDictionary
,连同 NSArray
是 Foundation 框架中最常用的几个集合类型。和其它标准库不同的是,它们的实现细节没有对开发者公开,使得开发者只能编写简单的代码,相信框架(在合理的程度上)是高效的。
然而,再好的抽象也有不好用的时候。当对他们底层的实现假设不符合预期的时候,开发者要么继续在抽象层次上进行探索,要么在可能的情况下,使用更加通用的解决方案。
对于 NSSet
和 NSDictionary
来说,不符合预期的部分通常在于它们存储对象时在内存中的表现。对于 NSSet
,对象在存储时会被强引用,NSDictionary
中值的存储也是一样。对键来说,在 NSDictionary
中会被拷贝。如果开发者想存储弱引用的值,或者使用一个没有遵守 <NSCopying>
的对象作为键,他可以选择聪明的办法,使用 NSValue +value
。或者,在 iOS 6(以及 OS X Leopard)上,他可以使用 NSHash
或 NSMap
,分别对应着 NSSet
和 NSDictionary
,是它们更加通用的版本。
废话不多说,对这两个 Foundation 框架中最不知名的集合类型,下面是你所需要知道的一切:
NSHash Table
NSHash
是 NSSet
的通用版本,和 NSSet
/ NSMutable
不同的是,NSHash
具有下面这些特性:
-
NSSet
/NSMutable
持有成员的强引用,通过Set hash
和is
方法来检测成员的散列值和相等性。Equal: -
NSHash
是可变的,没有不可变的对应版本。Table -
NSHash
可以持有成员的弱引用。Table -
NSHash
可以在加入成员时进行Table copy
操作。 -
NSHash
可以存储任意的指针,通过指针来进行相等性和散列检查。Table
用法
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
对象在初始化时可以选择下面任意一个选项来产生不同的行为。在 NSHash
从具有垃圾回收机制的 OS X 环境被移植到 ARC 化的 iOS 环境的过程中,有一些选项枚举值被废弃了。其余的选项值对应着 NSPointerFunctions 的选项,这部分内容会在下周的 NSHipster 中进行讲解。(译者注:下面具体的内容来自官方文档,不再做翻译,NSMapTable 部分做相同处理)
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
是 NSDictionary
的通用版本。和 NSDictionary
/ NSMutable
不同的是,NSMap
具有下面这些特性:
-
NSDictionary
/NSMutable
对键进行拷贝,对值持有强引用。Dictionary -
NSMap
是可变的,没有不可变的对应版本。Table -
NSMap
可以持有键和值的弱引用,当键或者值当中的一个被释放时,整个这一项就会被移除掉。Table -
NSMap
可以在加入成员时进行Table copy
操作。 -
NSMap
可以存储任意的指针,通过指针来进行相等性和散列检查。Table
注意:
NSMap
专注于强引用和弱引用,意味着 Swift 中流行的值类型是不适用的,只能用于引用类型。Table
用法
下面的例子展示了如何使用 NSMap
来包含不可拷贝的键,以及存储键对应的 delegate 或其他值的弱引用。
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
对象在初始化时需要使用下面这些选项来指定键和值的具体行为:
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
使用下标
NSMap
没有实现对象下标索引,不过通过 category 来添加这个特性并不是很麻烦。NSDictionary
对于键要遵守 NSCopying
的要求,只适用于 NSDictionary
本身:
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
和往常一样,记住一点,编程并不是要做到多么聪明:永远先从最高的抽象层次去尝试解决问题。NSSet
和 NSDictionary
都是 非常好 的工具。在 99% 的情况下,它们毋庸置疑是正确的选择。如果你碰到的问题包含上面提到的具体的内存管理需求,那么 NSHash
和 NSMap
值得你一看。