使用非可复制对象作为NSMutableDictionary的键?

时间:2010-08-18 05:06:24

标签: objective-c key nsmutabledictionary

我试图弄清楚这段代码的引用:Cocoa: Dictionary with enum keys?

+ (NSValue*)valueWithReference:(id)target
{
    return [NSValue valueWithBytes:&target objCType:@encode(id*)];
}

[table setObject:anObject forKey:[NSValue valueWithReference:keyObject]];

但感觉不好。有什么建议吗?

3 个答案:

答案 0 :(得分:9)

你说得对,这不好。

首先,您编码的是错误的类型(它应该是@encode(id),而不是@encode(id*)),但在大多数情况下,这不会导致大问题。

更大的问题是,这完全忽略了内存管理。该对象不会被保留或复制。如果其他一些代码释放它,它可能会消失,然后你的字典键将是一个盒装指针,指向垃圾甚至完全不同的对象。这基本上是世界上最先进的dangling pointer

您有两个不错的选择:

  1. 您可以将NSCopying添加到类中,也可以创建可复制的子类。

    • 此选项仅适用于可以有意义复制的对象。这是大多数类,但不一定全部(例如,有多个对象表示相同的输入流可能不好)
    • 即使对于有意义的课程,实施复制也会很痛苦 - 本身并不困难,但有点讨厌
  2. 您可以使用CFDictionary API创建字典。由于Core Foundation类型没有通用的复制功能,因此CFDictionary默认只保留其键(尽管您可以根据需要自定义其行为)。但CFDictionary也与NSDictionary免费联系,这意味着您可以将CFDictionaryRef转换为NSDictionary*(或NSMutableDictionary*),然后将其视为任何其他NSDictionary。

    • 这意味着您用作键的对象不得更改(至少不会影响其hash值),而它在字典中 - 确保不会发生这种情况是为什么NSDictionary通常想要复制其密钥

答案 1 :(得分:1)

供以后参考。

现在我知道还有更多选择。

  1. 覆盖NSCopying协议中的方法,并返回self而不是复制自身。 (如果您不使用ARC,则应保留它)同时确保对象始终为-hash方法返回相同的值。

  2. 使可复制的简单容器类保持对原始密钥对象的强引用。容器是可复制的,但它只是在复制时传递原始密钥。覆盖等式/哈希方法也匹配语义。即使只是NSArray的实例,也只包含关键对象。

  3. 方法#1看起来很安全,但实际上我不确定这是安全的。因为我不知道NSDictionary的内部行为。所以我通常使用#2方式,这在Cocoa约定中是完全安全的。

    <强>更新

    现在,自版本6.0起,我们在iOS中也有NSHashTableNSMapTable

答案 2 :(得分:0)

我不是100%肯定这个解决方案的正确性,但我发布它是为了以防万一。

如果你不想使用CFDictionary,也许你可以使用这个简单的类别:

@implementation NSMutableDictionary(NonCopyableKeys)
- (void)setObject:(id)anObject forNonCopyableKey:(id)aKey {
    [self setObject:anObject forKey:[NSValue valueWithPointer:aKey]];
}

- (id)objectForNonCopyableKey:(id)aKey {
    return [self objectForKey:[NSValue valueWithPointer:aKey]];
}

- (void)removeObjectForNonCopyableKey:(id)aKey {
    [self removeObjectForKey:[NSValue valueWithPointer:aKey]];
}
@end

这是我在网上看到的类似方法(无法找到原始资源)的概括,用于使用可以使用UITouch密钥存储对象的NSMutableDictionary。

与Chuck的答案相同的限制适用:您用作键的对象不得以影响其哈希值的方式更改,并且在字典中不得释放。

另外请确保不要混用-(void)setObject:(id)anObject forNonCopyableKey:(id)aKey- (id)objectForKey:(id)aKey方法,因为它不起作用(后者将返回nil)。

这似乎工作正常,但可能会有一些我不想的副作用。如果有人发现此解决方案有任何其他问题或警告,请发表评论。