NSMapTable 是早在 Mac OS X 10.5(Leopard)就引入的集合类型。乍一看,这似乎是作为一个替换 NSDictionary 的存在,可以选择
strong
和week
指针。在这篇文章中,我会告诉你为什么它也非常有用,及其垃圾回收机制以及它是如何做到 NSDictionary 不能(或不应该)做的事情。
Leopard 中其他 Cocoa API
Cocoa 增加了几个新的集合类类型在 Mac OS X 10.5(Leopard):
NSPointerArray 是全新的,但大部分如 NSHashTable
和 NSMapTable
的功能之前可从 Foundation 中看到影子。
在某些方面,这些新的类型,像 NSMutableArray
、NSMutableSet
和 NSMutableDictionary
一样,但是给了你使用 week
指针来使用垃圾回收的选择。如果您使用的 Objective-C 2.0 的垃圾回收机制,你应该知道什么是 week
指针,所以使用它的好处是很明显的。
NSPointerArray 也可用于纯指针(不一定是 OC 的类的指针),但 NSHashTable
和的 NSMutableArray
类都需要它们的内容是 OC 的对象。
虽然在一般意义上,NSPointerArray
和 NSHashTable
被设计为可以替换 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 不指定(默认行为)
hash
与isEqualTo:
方法将在key
上调用以确定 NSMapTable 中的存储位置。 这些方法的返回值在 NSMapTable 中使用密钥的持续时间不应该改变(不可变)。
这两种行为都意味着内容实现了 NSObject 协议,因此该协议中的方法也可以在 key
和 obj
上被调用。 并且可以在 NSMapTable 包含的 key
和对象上调用描述方法,而不管所使用的选项如何。 如果所有的密钥和对象都实现了 NSCoding 协议,NSMapTable 只支持 NSCoding。
Copy options
如果指定了 NSMapTableCopyIn,则 NSMapTable 在添加时使用 NSCopying 协议创建自己的数据副本。 如果不指定此选项(默认行为),则不会复制。
翻译自:NSMapTable: more than an NSDictionary for weak pointers
这篇文章虽然很久了(2008 年),但就算放在当下也是很有学习价值的,感谢原文作者的分享!
修订:2017.3.23