Swift数组是在写入时复制的值类型。如果原始数组未发生变异,则“复制”指向相同的内存位置。
假设我们有一个由多个线程引用的类
class Foo {
var numbers: [Int] = [1, 2, 3]
}
let foo = Foo()
如果主题A“复制”numbers
var numbers = foo.numbers
然后后来的线程B用不同的数组实例替换numbers
foo.numbers = [4, 5, 6]
原始数字数组([1, 2, 3]
)是否会被释放,当线程B尝试访问其元素时会导致incorrect checksum for freed object - object was probably modified after being freed
?
答案 0 :(得分:1)
var numbers = foo.numbers
之前, [1, 2, 3]
将始终包含var numbers
。
let foo = Foo()
// Thread A
var numbers = foo.numbers
// Thread b
foo.numbers = [4, 5, 6]
// Thread A again - 'var numbers' still has a "reference" to [1, 2, 3]
print(numbers) // "[1, 2, 3]"
我认为你误解了swift中的结构如何作为价值类型。重新分配[1, 2, 3]
时,原始的foo.numbers
数组不会被覆盖。
假设您有10个线程,其中所有“复制”foo.numbers
,此时不会复制数组的内存。
var myTheadNumbers = foo.numbers
让我们说线程1修改数组
myThreadNumbers.append(4)
[1, 2, 3]
被复制到一个新数组,然后通过附加4来修改。
线程1现在拥有一个[1, 2, 3, 4]
的单个数组,而线程2-10仍然共享原始[1, 2, 3]
。这就是用于结构的copy-on-write意味着 - 当你修改一个结构而另一个人有一个旧的副本时,你只会获得复制的性能。
这10个主题中的每一个都有自己的[1, 2, 3]
副本,这就是值类型的含义。
答案 1 :(得分:0)
首先,考虑一下没有并发的情况:
在删除所有强引用之前,不会释放原始数组,因此只要numbers
仍保留引用,原始数组将在内存中。
将新数组分配给foo.numbers
时,删除对原始数组的一个引用,但numbers
仍保留引用,因此不会释放它。 foo.numbers
现在对新数组有强烈的引用。
即使我修改了foo.numbers
:
foo.numbers.append(4)
numbers
仍将保留对原始数组的引用,foo.numbers
现在将保留对新数组的引用,因为它在修改时被复制。
一旦你将并发性引入所有这些,它就会变得混乱,因为这些复制和赋值操作可能不是原子的。任何对象的并发更新都需要保护关键部分以防止损坏。
您可以使用串行调度队列来管理numbers
数组的任何更新。
class Foo {
private var _numbers: [Int] = [1,2,3];
private let serialQ = dispatch_queue_create("serialQ", DISPATCH_QUEUE_SERIAL)
var numbers: [Int] {
set {
dispatch_sync(self.serialQ) {
self._numbers = newValue
}
}
get {
return self._numbers
}
}
}