NSHash​Table & NSMap​Table

NSSetNSDictionary,连同 NSArray 是 Foundation 框架中最常用的几个集合类型。和其它标准库不同的是,它们的实现细节没有对开发者公开,使得开发者只能编写简单的代码,相信框架(在合理的程度上)是高效的。

然而,再好的抽象也有不好用的时候。当对他们底层的实现假设不符合预期的时候,开发者要么继续在抽象层次上进行探索,要么在可能的情况下,使用更加通用的解决方案。

对于 NSSetNSDictionary 来说,不符合预期的部分通常在于它们存储对象时在内存中的表现。对于 NSSet,对象在存储时会被强引用,NSDictionary 中值的存储也是一样。对键来说,在 NSDictionary 中会被拷贝。如果开发者想存储弱引用的值,或者使用一个没有遵守 <NSCopying> 的对象作为键,他可以选择聪明的办法,使用 NSValue +valueWithNonretainedObject。或者,在 iOS 6(以及 OS X Leopard)上,他可以使用 NSHashTableNSMapTable,分别对应着 NSSetNSDictionary,是它们更加通用的版本。

废话不多说,对这两个 Foundation 框架中最不知名的集合类型,下面是你所需要知道的一切:

NSHashTable

NSHashTableNSSet 的通用版本,和 NSSet / NSMutableSet 不同的是,NSHashTable 具有下面这些特性:

  • NSSet / NSMutableSet 持有成员的强引用,通过 hashisEqual: 方法来检测成员的散列值和相等性。
  • NSHashTable 是可变的,没有不可变的对应版本。
  • NSHashTable 可以持有成员的弱引用。
  • NSHashTable 可以在加入成员时进行 copy 操作。
  • NSHashTable 可以存储任意的指针,通过指针来进行相等性和散列检查。

用法

let hashTable = NSHashTable(options: .CopyIn)
hashTable.addObject("foo")
hashTable.addObject("bar")
hashTable.addObject(42)
hashTable.removeObject("bar")
print("Members: \(hashTable.allObjects)")

NSHashTable 对象在初始化时可以选择下面任意一个选项来产生不同的行为。在 NSHashTable 从具有垃圾回收机制的 OS X 环境被移植到 ARC 化的 iOS 环境的过程中,有一些选项枚举值被废弃了。其余的选项值对应着 NSPointerFunctions 的选项,这部分内容会在下周的 NSHipster 中进行讲解。(译者注:下面具体的内容来自官方文档,不再做翻译,NSMapTable 部分做相同处理)

  • NSHashTableStrongMemory: Equal to NSPointerFunctionsStrongMemory. This is the default behavior, equivalent to NSSet member storage.
  • NSHashTableWeakMemory: Equal to NSPointerFunctionsWeakMemory. Uses weak read and write barriers. Using NSPointerFunctionsWeakMemory, object references will turn to NULL on last release.
  • NSHashTableZeroingWeakMemory: This option has been deprecated. Instead use the NSHashTableWeakMemory option.
  • NSHashTableCopyIn: Use the memory acquire function to allocate and copy items on input (see NSPointerFunction -acquireFunction). Equal to NSPointerFunctionsCopyIn.
  • NSHashTableObjectPointerPersonality: Use shifted pointer for the hash value and direct comparison to determine equality; use the description method for a description. Equal to NSPointerFunctionsObjectPointerPersonality.

NSMapTable

NSMapTableNSDictionary 的通用版本。和 NSDictionary / NSMutableDictionary 不同的是,NSMapTable 具有下面这些特性:

  • NSDictionary / NSMutableDictionary 对键进行拷贝,对值持有强引用。
  • NSMapTable 是可变的,没有不可变的对应版本。
  • NSMapTable 可以持有键和值的弱引用,当键或者值当中的一个被释放时,整个这一项就会被移除掉。
  • NSMapTable 可以在加入成员时进行 copy 操作。
  • NSMapTable 可以存储任意的指针,通过指针来进行相等性和散列检查。

注意: NSMapTable 专注于强引用和弱引用,意味着 Swift 中流行的值类型是不适用的,只能用于引用类型。

用法

下面的例子展示了如何使用 NSMapTable 来包含不可拷贝的键,以及存储键对应的 delegate 或其他值的弱引用。

let delegate: AnyObject = ...
let mapTable = NSMapTable(keyOptions: .StrongMemory, valueOptions: .WeakMemory)

mapTable.setObject(delegate, forKey: "foo")
print("Keys: \(mapTable.keyEnumerator().allObjects)")

NSMapTable 对象在初始化时需要使用下面这些选项来指定键和值的具体行为:

  • 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 NSMapTableZeroingWeakMemory.
  • NSHashTableZeroingWeakMemory: This option has been superseded by the NSMapTableWeakMemory option.
  • NSMapTableCopyIn: Use 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 NSPointerFunctionsObjectPointerPersonality.

使用下标

NSMapTable 没有实现对象下标索引,不过通过 category 来添加这个特性并不是很麻烦。NSDictionary 对于键要遵守 NSCopying 的要求,只适用于 NSDictionary 本身:

extension NSMapTable {
    subscript(key: AnyObject) -> AnyObject? {
        get {
            return objectForKey(key)
        }

        set {
            if newValue != nil {
                setObject(newValue, forKey: key)
            } else {
                removeObjectForKey(key)
            }
        }
    }
}

和往常一样,记住一点,编程并不是要做到多么聪明:永远先从最高的抽象层次去尝试解决问题。NSSetNSDictionary 都是 非常好 的工具。在 99% 的情况下,它们毋庸置疑是正确的选择。如果你碰到的问题包含上面提到的具体的内存管理需求,那么 NSHashTableNSMapTable 值得你一看。

作者 Mattt
Mattt

Mattt (@mattt) is a writer and developer in Portland, Oregon. He is the founder of NSHipster and Flight School, and the creator of several open source libraries, including AFNetworking and Alamofire.

翻译者
Chester Liu

iOS 工程师,一直想成为更好的自己。我的 GithubStackOverflow

下一篇文章

在哲学和数学领域,相等的概念一直以来都是人们所争论和探寻的焦点,其所蕴含的深层含义更是延伸到道德,社会正义以及公共政策等诸多层面。对于程序员来说,在我们所建模的问题领域当中,协调好“相等”概念在逻辑上和实际意义上的理解,是一个很重要的任务。