NSMapTable: 不只是一个能放weak指针的 NSDictionary

NSMapTable 是早在 Mac OS X 10.5(Leopard)就引入的集合类型。乍一看,这似乎是作为一个替换 NSDictionary 的存在,可以选择 strongweek 指针。在这篇文章中,我会告诉你为什么它也非常有用,及其垃圾回收机制以及它是如何做到 NSDictionary 不能(或不应该)做的事情。

Leopard 中其他 Cocoa API

Cocoa 增加了几个新的集合类类型在 Mac OS X 10.5(Leopard):

NSPointerArray 是全新的,但大部分如 NSHashTableNSMapTable 的功能之前可从 Foundation 中看到影子。

在某些方面,这些新的类型,像 NSMutableArrayNSMutableSetNSMutableDictionary 一样,但是给了你使用 week 指针来使用垃圾回收的选择。如果您使用的 Objective-C 2.0 的垃圾回收机制,你应该知道什么是 week 指针,所以使用它的好处是很明显的。

NSPointerArray 也可用于纯指针(不一定是 OC 的类的指针),但 NSHashTable 和的 NSMutableArray 类都需要它们的内容是 OC 的对象。

虽然在一般意义上,NSPointerArrayNSHashTable 被设计为可以替换 NSMutableArray and NSMutableSet 的角色(有序和无序集合)。 但 NSMapTable 则不同,因为它可以补充 NSMutableDictionary 不能(或不应该)做的事情。

NSDictionary 的局限性

NSDictionary 提供了 key -> object 的映射。从本质上讲,NSDictionary 中存储的 object 位置是由 key 来索引的。

由于对象存储在特定位置,NSDictionary 中要求 key 的值不能改变(否则 object 的位置会错误)。为了保证这一点,NSDictionary 会始终复制 key 到自己私有空间。

这个 key 的复制行为也是 NSDictionary 如何工作的基础,但这也有一个限制:你只能使用 OC 对象作为 NSDictionary 的 key,并且必须支持 NSCopying 协议。此外,key 应该是小且高效的,以至于复制的时候不会对 CPU 和内存造成负担。

这意味着,NSDictionary 中真的只适合将值类型的对象作为 key(如简短字符串和数字)。并不适合自己的模型类来做对象到对象的映射。

对象到对象的映射

NSMapTable(顾名思义)更适合于一般来说的映射概念。这取决于它的设计方式,NSMapTable 可以处理的 key -> obj 式映射如 NSDictionary,但它也可以处理 obj -> obj 的映射 - 也被称为 “关联数组” 或简称为 “map”。

比如一个 NSMapTable 的构造如下:

NSMapTable *keyToObjectMapping =
    [NSMapTable mapTableWithKeyOptions:NSMapTableCopyIn
                          valueOptions:NSMapTableStrongMemory];

这将会和 NSMutableDictionary 用起来一样一样的,复制 key,并对它的 object 引用计数 +1。

一个真正的对象到对象(object-to-object)的映射可以构造如下:

NSMapTable *objectToObjectMapping = [NSMapTable mapTableWithStrongToStrongObjects];

一个对象到对象(object-to-object)的行为可能以前可以用 NSDictionary 来模拟,如果所有的 key 都是一个 NSNumber 来指向该映射的源对象的内存地址(不要笑,我见过),但这些内存地址都是不可控的,Cocoa 中首次提供了一个真正的对象到对象的映射集合类型那就是 NSMapTable。

NSMapTable 的选项

NSMapTable 提供的选项是由三部分组成:一个 “memory option”(内存选项),一个 “personality option” 和 “copy in” 标志。你可以为每个部分进行设置(如果没有设置将会使用默认值),这些选项都是位标志(bit flag)(二进制 “or” 合并在一起)。

理论上,NSMapTable 允许以下选项:

  • NSMapTableStrongMemory (a “memory option”)
  • NSMapTableWeakMemory (a “memory option”)
  • NSMapTableObjectPointerPersonality (a “personality option”)
  • NSMapTableCopyIn (a “copy option”)

NSMapTableStrongMemory 是默认的 “memory option”。然而,默认的“personality option”,默认“copy in”的行为没有名字那么这两个值可以被视为隐含在列表中。

memory option

Objective-C 使用 “strong” 和 “week” 作为垃圾回收机制相关的关键字,这些选项可能在不使用垃圾回收机制时并不重要(苹果称它为手动内存管理)。

如不用垃圾回收机制,他们被定义为:

  • strong: 使用 retain 和 release
  • weak: 不使用 retain 和 release

NSMapTable 只允许 NSPointerFunctionsOptions 对应的 Objective-C 对象 “personality option”。还有 NSPointerFunctionsOptions “personality option” 里的 “strong” 指针的行为不包括 retain 和 release,但这些选项在 NSMapTable 都是不允许的。

关于不使用垃圾回收机制时 “week” 的警告: 指针将不会被归零如在垃圾回收环境,所以你必须要小心,如果它被释放不要取消引用指针。

Personality options

NSMapTableObjectPointerPersonality 选项用于控制在将对象添加到集合中时是否调用对象上的 isEqualTo:hash 方法。

  • NSMapTableObjectPointerPersonality 指定 对象的指针的值是用于直接比较和位移哈希生成(isEqualTo:且不用 hash 方法)。
  • NSMapTableObjectPointerPersonality 不指定(默认行为) hashisEqualTo: 方法将在 key 上调用以确定 NSMapTable 中的存储位置。 这些方法的返回值在 NSMapTable 中使用密钥的持续时间不应该改变(不可变)。

这两种行为都意味着内容实现了 NSObject 协议,因此该协议中的方法也可以在 keyobj 上被调用。 并且可以在 NSMapTable 包含的 key 和对象上调用描述方法,而不管所使用的选项如何。 如果所有的密钥和对象都实现了 NSCoding 协议,NSMapTable 只支持 NSCoding。

Copy options

如果指定了 NSMapTableCopyIn,则 NSMapTable 在添加时使用 NSCopying 协议创建自己的数据副本。 如果不指定此选项(默认行为),则不会复制。


翻译自:NSMapTable: more than an NSDictionary for weak pointers

这篇文章虽然很久了(2008 年),但就算放在当下也是很有学习价值的,感谢原文作者的分享!

修订:2017.3.23

comments powered by Disqus