假设我有一个具有Array
对象Photo
的课程:
class PhotoManager {
fileprivate var _photos: [Photo] = []
var photos: [Photo] {
return _photos
}
}
我读了one article,其中说明了以下内容:
默认情况下,Swift类实例通过引用传递 结构通过值传递。 Swift的内置数据类型,如Array和 字典,作为结构实现。
意味着上面的getter返回[Photo]
数组的副本。
然后,同一篇文章试图通过将代码重构为:
来使getter线程安全fileprivate let concurrentPhotoQueue = DispatchQueue(label: "com.raywenderlich.GooglyPuff.photoQueue",
attributes: .concurrent)
fileprivate var _photos: [Photo] = []
var photos: [Photo] {
var photosCopy: [Photo]!
concurrentPhotoQueue.sync {
photosCopy = self._photos
}
return photosCopy
}
上述代码明确地在getter中复制了self._photos
。
我的问题是:
如果默认情况下swift已经返回了一个副本(按值传递),就像文章首先说的那样,为什么文章会再次复制到photosCopy
以使其成为线程安全的?我觉得自己并不完全理解那篇文章中提到的这两部分。
对于Array
实例,Swift3是否真的按值传递,如文章所述?
有人可以帮我澄清一下吗?谢谢!
答案 0 :(得分:0)
1)在代码示例中,您提供的内容将是_photos
的返回副本。
如文章所述:
The getter for this property is termed a read method as it’s reading
the mutable array. The caller gets a copy of the array and is protected
against mutating the original array inappropriately.
这意味着您可以从课堂外访问_photos
,但您无法从那里更改它们。 photos
的值只能在类内部进行更改,以防止此数组意外更改。
2)是的,Array
是一个值类型结构,它将按值传递。您可以在Playground中轻松查看
let arrayA = [1, 2, 3]
var arrayB = arrayA
arrayB[1] = 4 //change second value of arrayB
print(arrayA) //but arrayA didn't change
UPD#1
在文章中,他们有方法func addPhoto(_ photo: Photo)
为_photos
数组添加新照片的原因是什么使得访问此属性不是线程安全的。这意味着_photos
的价值可以在几个线程中同时改变,这将导致问题。
他们通过在concurrentQueue
上用.barrier
写照片使其线程安全,_photos
数组每次更改一次
func addPhoto(_ photo: Photo) {
concurrentPhotoQueue.async(flags: .barrier) { // 1
self._photos.append(photo) // 2
DispatchQueue.main.async { // 3
self.postContentAddedNotification()
}
}
}
现在为了确保线程安全,您需要在同一队列中读取_photos
数组。这是他们重构读取方法
答案 1 :(得分:0)
我会反过来解答你的问题:
- 对于Array实例,Swift3是否真的按值传递值,如文章所述?
醇>
简单回答:是的
但我猜这不是你在关注“Swift3 真的通过值传递”时所关注的问题。 Swift的行为好像数组完全被复制,但在幕后它优化了操作,整个数组不会被复制,直到它需要。 Swift使用称为写时复制(COW)的优化。
然而对于Swift程序员如何复制完成并不像操作的语义那么重要 - 这是在赋值/复制之后两个数组是独立的和变异的不影响另一个。
- 如果默认情况下swift已经返回了一个副本(按值传递),就像文章首先说的那样,为什么文章会再次复制到photosCopy以使其成为线程安全的?我觉得自己并不完全理解那篇文章中提到的这两部分。
醇>
此代码正在做的是确保副本以线程安全的方式完成。
数组不是一个微不足道的值,它实现为多字段结构,其中一些字段引用其他结构和/或对象 - 这是支持数组扩展大小等功能所必需的。
在多线程系统中,一个线程可能会尝试复制该阵列,而另一个线程正在尝试更改该阵列。如果允许这些同时发生,则事情很容易出错,例如,复制过程中阵列可能会发生变化,导致副本无效 - 部分旧值,部分新值。
Swift 本身 不线程安全;特别是它不会阻止在执行复制时更改数组。您使用GCD队列解决此问题的代码,以便在通过一个线程对阵列进行任何更改期间,阻止对任何其他线程中的阵列的所有其他写入或读取,直到更改完成。
您可能还担心他们会在这里发送多份副本,self._photos
到photoCopy
,然后photoCopy
到返回值。虽然语义上这是实践中发生的事情,但可能只有一个复杂的副本(这将是线程安全的),因为Swift系统将进行优化。
HTH