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];
让我们假设字典没有复制密钥,而只是retain
ed 它。如果现在,在稍后的某个时间点,原始字符串被修改,那么即使您使用相同的键对象(即 key
指向上例中的)。
为了保护自己免受此类错误的影响,字典会复制所有键。
注意,顺便说一下,将-copyWithZone:
定义为return [self retain]
很简单。如果您的对象是不可变的,那么这是允许的并且是好的代码,并且 NSCopying
合约是专门设计的,使得返回的对象必须是(有点,有点)不可变的:
当类及其内容不可变时,通过保留原始副本而不是创建新副本来实现 NSCopying。
(来自NSCopying Reference)
和
如果“不可变与可变”的考虑适用于接收对象,则返回的副本是不可变的;否则副本的确切性质由类确定。
(来自-copyWithZone: Reference)
即使您的对象不是不可变的,如果您只使用基于身份的相等/散列实现,即不受对象内部状态以任何方式影响的实现,您也可能会摆脱该实现。
【讨论】:
德克:谢谢你。我认为我误解的关键事实(你的回答导致我)是密钥的语义不是关于密钥对象 ref;它们与键的内容有关(因此键是字符串“foo”,无论它来自文字,还是由字符构造,等等)——这意味着语义比较,而不是指针比较。但是键 必须 然后被复制,因为可变键可能会改变。这给我留下的地方是,当我真的想将对象用作键时,并且我真的在追求引用相等时,我的方法不是破解;完全正确。 本:不完全是。如果您希望保留keyObject
,则该代码不会这样做,因为它将keyObject
视为指向任何东西的指针,不一定是对象,因此它无法知道它可以保留它。我不确定value:withObjCType:
是否会保留,即使通过了@encode(id)
。为了绝对确定它保留了您的密钥,您需要使用 NSMapTable (您暗示您不能这样做,因为您正在为 iPhone 开发),使用 CFDictionary 而不是 NSDictionary (在您使用字典的任何地方,而不仅仅是在创建时),或者创建自己的 NSValue 子类。
好点。感谢您提供额外的信息。考虑到这一点,当我使用这种模式时,我从不需要保留密钥。通常,其他一些集合实际上“拥有”我用作键的对象;我只是使用字典将它们有效地映射到其他相关对象。最坏的情况是关键对象会被释放,在字典中留下一个垃圾条目。我唯一需要注意的是枚举实际的键并将指针转回id
s。但是那个代码闻起来很糟糕,足以知道你在做一些危险的事情......【参考方案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 的描述 - 为啥有些键名带有引号?