为什么不 NSSet/NSMutableSet/NSCountedSet 强制将不可变对象作为条目?
Why don't NSSet/NSMutableSet/NSCountedSet force immutable objects as entries?
NSDictionary 键是 id 但集合的值只是 id,文档表明它们的值被保留。根据 Set Fundamentals of the Collection Programming Topics 文档:
You can, however, modify individual objects themselves (if they support modification).
如果您修改对象,这可能会影响对象的哈希值,从而影响查找。我假设 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
)}
我的看法:
集合可能基于散列值存储桶,当散列在集合后面被更改时,它不知道要做什么并且查找被破坏。缺少这方面的文档。
我的问题重述:
文档说你可以改变对象,这是不直观的。
改变对象会破坏集合。
什么鬼?
由于在 Objective-C 中确保对象不变性的唯一可靠方法是制作副本,Cocoa 设计人员有两个选择:
- 制作
NSSet
复制对象 - 这将是安全的,但由于内存使用量增加,它会严重限制 NSSet
的使用。
- 使用保留的对象 - 这将使内存使用量保持在最低限度,但它会给用户一种通过改变内部对象来搬起石头砸自己脚的方法
NSSet
.
设计人员选择了第二种方法而不是第一种方法,因为它修复了可以通过适当的编码技术避免的危险。相比之下,选择第一种方法对每个人来说都是 "binding",因为插入一个新对象总是会生成一个副本。
目前,用户可以选择插入他们手动创建的对象的副本,从而模拟第一种方法。但是,强制复制的实现无法模拟保留对象的实现,这使得它成为一个不太灵活的选择。
文档中的那一行令人困惑。但是,请注意下面的三段继续说:
If mutable objects are stored in a set, either the hash
method of the
objects shouldn’t depend on the internal state of the mutable objects
or the mutable objects shouldn’t be modified while they’re in the set.
For example, a mutable dictionary can be put in a set, but you must
not change it while it is in there. (Note that it can be difficult to
know whether or not a given object is in a collection).
您的代码演示的是已知的 属性 基于散列的集合 classes。它也会影响字典,如果一个关键对象被实现为复制 returns 原始的,它本质上是可变的。
没有真正的方法来测试对象是否可变。所以,它不能强制不变性。
此外,正如上面引述中提到的,可以创建一个可变的 class,其 hash
和相等性不受突变的影响。
最后,如果这些集合 classes 只能与可复制的 classes 一起使用并复制元素(就像字典复制它们的键)。集合用于表示关系等,如果您试图在对象之间建立关系,而是建立与单独副本的关系,则不会这样做。
NSDictionary 键是 id
You can, however, modify individual objects themselves (if they support modification).
如果您修改对象,这可能会影响对象的哈希值,从而影响查找。我假设 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
)}
我的看法:
集合可能基于散列值存储桶,当散列在集合后面被更改时,它不知道要做什么并且查找被破坏。缺少这方面的文档。
我的问题重述: 文档说你可以改变对象,这是不直观的。 改变对象会破坏集合。 什么鬼?
由于在 Objective-C 中确保对象不变性的唯一可靠方法是制作副本,Cocoa 设计人员有两个选择:
- 制作
NSSet
复制对象 - 这将是安全的,但由于内存使用量增加,它会严重限制NSSet
的使用。 - 使用保留的对象 - 这将使内存使用量保持在最低限度,但它会给用户一种通过改变内部对象来搬起石头砸自己脚的方法
NSSet
.
设计人员选择了第二种方法而不是第一种方法,因为它修复了可以通过适当的编码技术避免的危险。相比之下,选择第一种方法对每个人来说都是 "binding",因为插入一个新对象总是会生成一个副本。
目前,用户可以选择插入他们手动创建的对象的副本,从而模拟第一种方法。但是,强制复制的实现无法模拟保留对象的实现,这使得它成为一个不太灵活的选择。
文档中的那一行令人困惑。但是,请注意下面的三段继续说:
If mutable objects are stored in a set, either the
hash
method of the objects shouldn’t depend on the internal state of the mutable objects or the mutable objects shouldn’t be modified while they’re in the set. For example, a mutable dictionary can be put in a set, but you must not change it while it is in there. (Note that it can be difficult to know whether or not a given object is in a collection).
您的代码演示的是已知的 属性 基于散列的集合 classes。它也会影响字典,如果一个关键对象被实现为复制 returns 原始的,它本质上是可变的。
没有真正的方法来测试对象是否可变。所以,它不能强制不变性。
此外,正如上面引述中提到的,可以创建一个可变的 class,其 hash
和相等性不受突变的影响。
最后,如果这些集合 classes 只能与可复制的 classes 一起使用并复制元素(就像字典复制它们的键)。集合用于表示关系等,如果您试图在对象之间建立关系,而是建立与单独副本的关系,则不会这样做。