我的应用使用可变的自定义元素集。一旦我崩溃了,并发生了错误“在Set中发现重复元素”。插入后元素可能已经突变。”
在寻找解释时,我发现this post,但我并不完全理解。
我的印象是,不应修改集合的元素,因为这也会修改集合的哈希值,因此进一步的访问可能会失败。
我的问题:
编辑:
用不同的措词表达:修改可变集合的自定义元素的属性而不修改集合本身是否安全?
答案 0 :(得分:3)
Swift集的实现与字典类似,在Exploring Swift Dictionary's Implementation中有很好的描述。特别地,元素存储是“桶”的列表,每个桶都可以被占用或不被占用。将新元素插入集合后,其哈希值将用于确定初始存储桶。如果该存储桶已被占用,则将线性搜索下一个空闲存储桶。同样,在搜索集合中的元素时,哈希值用于确定初始存储桶,然后进行线性搜索,直到找到该元素(或未占用的存储桶)为止。
(详细信息可以在开源实现中找到,最相关的源文件是 Set.swift,NativeSet.swift,SetStorage.swift和HashTable.swift。)
更改插入的元素的哈希值将破坏集合存储实现的不变性:通过元素的初始存储桶定位元素不再起作用。突变影响相等性的其他属性可能导致同一存储桶列表中出现多个“相等”元素。
因此,我认为可以肯定地说
在将引用类型的实例插入集合后,不得以影响其哈希值或进行相等性测试的方式修改该实例的属性。
示例
首先,这仅是引用类型的集合的问题。值类型的集合包含值的独立副本,并且在插入后修改该值的属性不会影响集合:
struct Foo: Hashable {
var x: Int
}
var set = Set<Foo>()
var foo = Foo(x: 1)
set.insert(foo)
print(set.map { $0.x }) // [1]
foo.x = 2
print(set.map { $0.x }) // [1]
set.insert(foo)
print(set.map { $0.x }) // [1, 2]
引用类型的实例是实际对象存储的“指针”,并且修改该实例的属性不会使引用发生突变。因此,可以在将实例的属性插入集合后对其进行修改:
class Bar: Hashable {
var x : Int
init(x: Int) { self.x = x }
static func == (lhs: Bar, rhs: Bar) -> Bool { return lhs.x == rhs.x }
func hash(into hasher: inout Hasher) { hasher.combine(x) }
}
var set = Set<Bar>()
let bar = Bar(x: 1)
set.insert(bar)
print(set.map { $0.x }) // [1]
bar.x = 2
print(set.map { $0.x }) // [2]
但是,这很容易导致崩溃,例如如果我们再次插入相同的引用:
set.insert(bar)
Fatal error: Duplicate elements of type 'Bar' were found in a Set. This usually means either that the type violates Hashable's requirements, or that members of such a set were mutated after insertion.
这里是另一个示例,其中所有实例的哈希值都相同,但是修改用于相等性测试的属性会导致一组两个“相等”的实例:
class Baz: Hashable {
var x : Int
init(x: Int) { self.x = x }
static func == (lhs: Baz, rhs: Baz) -> Bool { return lhs.x == rhs.x }
func hash(into hasher: inout Hasher) { }
}
var set = Set<Baz>()
let baz1 = Baz(x: 1)
set.insert(baz1)
let baz2 = Baz(x: 2)
set.insert(baz2)
baz1.x = 2
print(set.map { $0.x }) // [2, 2]
print(set.count) // 2
print(Set(Array(set)).count) // 1
答案 1 :(得分:0)
允许的操作:您可以添加,删除和更新NSMutableSet
中的元素。如果要更新/添加元素,则必须调用.add()
方法,如果该方法尚不是成员,它将向集合中添加给定对象。
请查看here有关NSMutableSet的Apple文档。
您可以执行所有类型的操作,例如添加,删除,更新等。
答案 2 :(得分:-1)
基本上快速的stdlib是一种(经常)不可用的垃圾(类似于C ++ STL和其他c ++垃圾技术)。 NextStep传统中开箱即用的功能 天(如果有)在快速运行时(例如Set)将其正确设置。
我花了大约两个工作日来追踪人造哈希和==不匹配
放弃了Swift Set,转而使用NSMutableSet,现在我的应用程序还可以。
我提到快速运行时是垃圾吗?
此:
public extension NSSet
{
var isEmpty: Bool {
return count == 0
}
}
public extension NSMutableSet
{
func insert(_ item: Any)
{
add(item)
}
}
将帮助您无缝转换