Cocoa 的 NSDictionary:为啥要复制密钥?

Posted

技术标签:

【中文标题】Cocoa 的 NSDictionary:为啥要复制密钥?【英文标题】:Cocoa's NSDictionary: why are keys copied?Cocoa 的 NSDictionary:为什么要复制密钥? 【发布时间】:2010-03-06 21:00:50 【问题描述】:

在 NS(Mutable)Dictionaries 中用作键的所有对象都必须支持 NSCopying 协议,并且这些对象在字典中使用时会被复制。

我经常想使用较重的对象作为键,只是将一个对象映射到另一个对象。当我这样做时,我真正的意思是:

[dictionary setObject:someObject forKey:[NSValue valueWithPointer:keyObject]];

("当我再次将相同的关键对象实例交给您时,请给我同样的值。")

...这正是我有时为了绕过这个设计而最终做的事情。 (是的,我知道桌面 Cocoa 中的 NSMapTable;但例如 iPhone 不支持。)

但我没有真正明白为什么首先需要或需要复制密钥。它为实现或调用者买了什么?

【问题讨论】:

【参考方案1】:

副本确保用作键的值在用作键时不会“暗中”更改。考虑一个可变字符串的例子:

NSMutableString* key = ... 
NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];

[dict setObject: ... forKey: key];

让我们假设字典没有复制密钥,而只是retained 它。如果现在,在稍后的某个时间点,原始字符串被修改,那么即使您使用相同的键对象(即 key指向上例中的)。

为了保护自己免受此类错误的影响,字典会复制所有键。

注意,顺便说一下,将-copyWithZone: 定义为return [self retain] 很简单。如果您的对象是不可变的,那么这是允许的并且是好的代码,并且 NSCopying 合约是专门设计的,使得返回的对象必须是(有点,有点)不可变的:

当类及其内容不可变时,通过保留原始副本而不是创建新副本来实现 NSCopying。

(来自NSCopying Reference)

如果“不可变与可变”的考虑适用于接收对象,则返回的副本是不可变的;否则副本的确切性质由类确定。

(来自-copyWithZone: Reference)

即使您的对象不是不可变的,如果您只使用基于身份的相等/散列实现,即不受对象内部状态以任何方式影响的实现,您也可能会摆脱该实现。

【讨论】:

德克:谢谢你。我认为我误解的关键事实(你的回答导致我)是密钥的语义不是关于密钥对象 ref;它们与键的内容有关(因此键是字符串“foo”,无论它来自文字,还是由字符构造,等等)——这意味着语义比较,而不是指针比较。但是键 必须 然后被复制,因为可变键可能会改变。这给我留下的地方是,当我真的想将对象用作键时,并且我真的在追求引用相等时,我的方法不是破解;完全正确。 本:不完全是。如果您希望保留keyObject,则该代码不会这样做,因为它将keyObject 视为指向任何东西的指针,不一定是对象,因此它无法知道它可以保留它。我不确定value:withObjCType: 是否会保留,即使通过了@encode(id)。为了绝对确定它保留了您的密钥,您需要使用 NSMapTable (您暗示您不能这样做,因为您正在为 iPhone 开发),使用 CFDictionary 而不是 NSDictionary (在您使用字典的任何地方,而不仅仅是在创建时),或者创建自己的 NSValue 子类。 好点。感谢您提供额外的信息。考虑到这一点,当我使用这种模式时,我从不需要保留密钥。通常,其他一些集合实际上“拥有”我用作键的对象;我只是使用字典将它们有效地映射到其他相关对象。最坏的情况是关键对象会被释放,在字典中留下一个垃圾条目。我唯一需要注意的是枚举实际的键并将指针转回ids。但是那个代码闻起来很糟糕,足以知道你在做一些危险的事情......【参考方案2】:

如果您想将指针存储为键,则需要将它们包装在带有+valueWithPointer:NSValue 对象中。

【讨论】:

如果我错了,请纠正我,但是当我尝试实现它时它不起作用,因为我的指针引用的对象在我想访问它时已释放。我改用+valueWithNonretainedObject:【参考方案3】:

ios 6开始如果你想使用指针作为键,你可以使用NSMapTable对象,见http://nshipster.com/nshashtable-and-nsmaptable/

您可以指定键和/或值是强握还是弱握:

NSMapTable *mapTable = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory
                                         valueOptions:NSMapTableWeakMemory];

有时可能合适的另一个选项是使用NSCache,它可以牢固地保存密钥并且实际上是线程安全的。

【讨论】:

以上是关于Cocoa 的 NSDictionary:为啥要复制密钥?的主要内容,如果未能解决你的问题,请参考以下文章

NSDictionary 可能无法响应 NSMutableDictionary 对象上的 setObject:forKey

NSDictionary,这是为啥

cocoa:为啥活动没有运行?

NSDictionary 的描述 - 为啥有些键名带有引号?

为啥 NSDictionary 在使用 dictionaryWithObjectsAndKeys 时有时会删除引号?

字典:NSDictionary的应用举例