为什么NSSet / NSMutableSet / NSCountedSet不强制将不可变对象作为条目?

时间:2015-02-11 18:27:02

标签: objective-c cocoa

NSDictionary键是id< NSCopying>但是set的值只是id,而docs表明它们的值是保留的。根据{{​​3}}文档:

  

但是,您可以自行修改单个对象(如果它们支持修改)。

如果修改对象,则可能会影响对象的哈希值,这会影响查找。我假设NSSet是一个快速查找?

这是一个示例,展示了如果您改变对象时事情如何破坏:

    NSMutableString *str = [NSMutableString stringWithString: @"AWESOME"];
    NSCountedSet *countedSet = [[NSCountedSet alloc] init];
    [countedSet addObject: str];
    [countedSet addObject: str];

    NSLog(@"%@", @([countedSet countForObject: @"AWESOME"]));

    [str appendString: @" NOT AWESOME"];
    NSLog(@"%@", @([countedSet countForObject: @"AWESOME NOT AWESOME"]));
    NSLog(@"%@", @([countedSet countForObject: @"AWESOME"]));
    NSLog(@"%@", @([countedSet countForObject: str]));

    for(NSString *s in countedSet) {
        NSLog(@"%@ - %@", str, @([countedSet countForObject: s]));
    }

    NSSet *set = [NSSet setWithArray: @[ str ]];
    NSLog(@"Set Contains string, %@", @([set containsObject: str]));
    [str appendString: @"asdf"];
    NSLog(@"Set Contains string, %@", @([set containsObject: str]));
    NSLog(@"%@", set);

输出我的解释:

[64844:303] 2          // Count is 2
[64844:303] 0          // Count should be 2 - if it looks for the literal string
[64844:303] 0          // Count should be 0, but can't find original object either
[64844:303] 0          // Count should be 2 - asking for actual object that's in there
[64844:303] AWESOME NOT AWESOME - 0   // Should be 2 - asking for actual object that it just retrieved
[64844:303] Set Contains string, 1    // Correct, pre-mutation
[64844:303] Set Contains string, 0    // Should be true, object is in there
[65070:303] {(
    "AWESOME NOT AWESOMEasdf"   // see?  It's in there
)}

我的看法:

该集可能基于散列值的桶,当散列在集合后面更改时,它不知道该做什么以及查找被破坏。该领域缺乏文档。

我的问题重申: 文档说你可以改变对象,这是不直观的。 变异对象会破坏集合。 WTF?

2 个答案:

答案 0 :(得分:3)

来自文档的那一行令人困惑。然而,请注意它下面的三个段落继续说:

  

如果可变对象存储在集合中,则可以使用hash方法   对象不应该依赖于可变对象的内部状态   或者可变对象在它们处于集合中时不应被修改。   例如,可变字典可以放在一个集合中,但您必须   当它在那里时不要改变它。 (注意,它可能很难   知道给定对象是否在集合中。)

您的代码演示的内容是基于散列的集合类的已知属性。如果实现了一个关键对象,复制会返回原始内容,这本身就是可变的,也会影响字典。

没有真正的方法来测试对象是否可变。所以,它不能强迫不变性。

另外,正如上面引用中所提到的,可以创建一个hash和等式不受突变影响的可变类。

最后,如果它们只能与可复制类一起使用并制作元素的副本(如字典复制其键),则会严重限制这些集合类的实用程序。这些集合用于表示关系等,如果您尝试在对象之间建立关系,而不是建立与单独副本的关系,则不会这样做。

答案 1 :(得分:2)

由于在Objective-C中确保对象不变性的唯一可靠方法是制作副本,Cocoa设计师有两种选择:

  • NSSet复制对象 - 这样会安全,因为内存使用量增加会严重限制NSSet的使用。
  • 使用保留的对象 - 这样可以将内存使用率保持在最低限度,但它可以让用户通过改变NSSet内的对象来自己射击。 / LI>

设计师在第一种方法中选择了第二种方法,因为它解决了通过适当的编码技术可以避免的危险。相比之下,选择第一种方法将是“绑定”每个人,因为插入新对象总是会复制。

目前,用户可以选择插入他们手动创建的对象副本,从而模拟第一种方法。但是,强制复制的实现无法模拟保留对象的实现,从而使其成为不太灵活的选择。