【问题标题】:Using non-copyable object as key for NSMutableDictionary?使用不可复制对象作为 NSMutableDictionary 的键?
【发布时间】:2010-08-18 05:06:23
【问题描述】:

我试图找出这段代码引用:Cocoa: Dictionary with enum keys?

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

还有,

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

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

【问题讨论】:

  • 在 OSX 或 iOS6 上,只需使用 NSMapTable。
  • @Elden 是的,我希望它在那里。

标签: objective-c key nsmutabledictionary


【解决方案1】:

你说得对,这不好。

一方面,您编码的类型错误(应该是@encode(id),而不是@encode(id*)),但在大多数情况下,这不会造成大问题。

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

你有两个不错的选择:

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

    • 此选项仅适用于可以有意义地复制的对象。这是大多数类,但不一定是所有类(例如,让多个对象代表同一个输入流可能很糟糕)
    • 即使对于有意义的课程,实施复制也可能会很痛苦 - 本身并不困难,但有点烦人
  2. 您可以改为使用CFDictionary API 创建字典。由于 Core Foundation 类型没有通用的复制功能,CFDictionary 默认只保留它的键(尽管你可以随意定制它的行为)。但是 CFDictionary 也与 NSDictionary 进行免费桥接,这意味着您只需将 CFDictionaryRef 转换为 NSDictionary*(或 NSMutableDictionary*),然后像任何其他 NSDictionary 一样对待它。

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

【讨论】:

  • CFDictionary 选项很棒!谢谢!
  • 如果我使用[NSValue valueWithBytes:&&target objCType:@encode(id*)]; 来制作类似NSNotificationCenter 的东西(不保留/复制观察者),可以吗? (我又加了1个&操作员)
  • @Eonil: &&target 不是有效的 C 表达式。如果你打算这样做,你会想要使用&target@encode(id)。但是,是的,你可以这样做。仍然不确定它是否是理想的结构,但它应该可以工作。
  • Chuck:我针对这个问题发布了一个不同的(而且,依我的拙见,对于不了解 CF API 的人来说更简单)的解决方案。您能否看看我的替代方案是否有任何问题?
  • 如果CFDictionaryRef是基于hash的,我们可以使用
【解决方案2】:

供以后参考。

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

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

  2. 使可复制的简单容器类拥有对原始键对象的强引用。容器是可复制的,但它只是在复制时传递原始密钥。覆盖相等/散列方法也匹配语义。即使只是 NSArray 的一个实例只包含关键对象也能正常工作。

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

更新

现在我们在 iOS 6.0 中也有 NSHashTableNSMapTable

【讨论】:

    【解决方案3】:

    我不能 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)。

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

    【讨论】:

    • 这行不通,因为对象有特殊的内存管理语义。由于这将对象视为不透明的指针,因此这可能会给您留下一个充满悬空指针值的字典。例如,[dict setObject:@"something" forNonCopyableKey:[NSPipe pipe]] 最终会导致您的字典工作不正确或彻底崩溃您的程序。
    • 实际上,现在重新阅读问题后,这与 Eonil 所询问的技术完全相同(valueWithPointer: 本质上是 Eonil 使用的 valueWithBytes:objCType: 方法的便捷包装)。
    • 查克:感谢您的澄清。我承认这不适用于自动释放的对象。但是,它不适用于保留对象吗? (例如,NSPipe *pipe = [[NSPipe pipe] retain]; [dict setObject:@"something" forNonCopyableKey:pipe]。我的直觉是,只要您在使用NSDictionary 时不执行[pipe release],它就可以正常工作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-05
    • 2012-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多