寫時復制
在swift中忧便,像Array队他、Dictionary捏境、Set等集合類型都是通過寫時復制(copy-on-write)技術實現的。
例如一個整形數組
let x = [1,2,3,4]
var y = x
創(chuàng)建新變量 y窖壕,把 x 賦值給 y 會發(fā)生復制忧勿,現在x和y都是獨立的結構體。然后這些Array 結構體含有指向某個內存的引用瞻讽。這個內存就是數組元素存儲在堆上的位置鸳吸。我們可以通過 swift Array 的
@inlinable public func withUnsafeBufferPointer<R>(_ body: (UnsafeBufferPointer<Element>) throws -> R) rethrows -> R
來獲取 Array 元素存儲的地址指針,我們可以看出數組x和y存儲地址和元素確實都是相同的
let xPoints1 = x.withUnsafeBufferPointer{$0}
let yPoints1 = y.withUnsafeBufferPointer{$0}
print(xPoints1, x) // UnsafeBufferPointer(start: 0x000060000143b1e0, count: 4) [1, 2, 3, 4]
print(yPoints1, y) // UnsafeBufferPointer(start: 0x000060000143b1e0, count: 4) [1, 2, 3, 4]
然后我們對數組 y 進行操作:
let xPoints2 = x.withUnsafeBufferPointer{$0}
let yPoints2 = y.withUnsafeBufferPointer{$0}
y.append(5)
let xPoints2 = x.withUnsafeBufferPointer{$0}
let yPoints2 = y.withUnsafeBufferPointer{$0}
print(xPoints2, x) // UnsafeBufferPointer(start: 0x000060000143b1e0, count: 4) [1, 2, 3, 4]
print(yPoints2, y) // UnsafeBufferPointer(start: 0x0000600002516a80, count: 5) [1, 2, 3, 4, 5]
通過以上代碼可以看出速勇,當y引用的存儲要改變時晌砾,存儲進行了復制。這就是所謂的"寫時復制"烦磁。
但是作為一個結構體的作者养匈,你不能免費獲得這種特性哼勇,你需要自己進行實現。當結構類型內部含有一個或者多個可變引用呕乎,同時想要保持值語義猴蹂,并且不必要的復制,為你的類型實現寫時復制是有意義的楣嘁。
接下來磅轻,我們用 NSMutableArray 作為內部引用類型來實現 swift 中的值類型的 Array 結構體。
寫實復制(昂貴模式)
我們首先將 _array 標記為結構體的私有屬性逐虚。我們不再直接變更 _array聋溜,而
是通過一個計算屬性 _arrayForWriting 來訪問它。這個計算屬性總是會復制 _array 并將其返回:
struct MyArray {
fileprivate var _array: NSMutableArray
fileprivate var _arrayForWriting: NSMutableArray {
mutating get {
_array = _array.mutableCopy() as! NSMutableArray
return _array
}
}
init(_ array: NSMutableArray) {
self._array = array.mutableCopy() as! NSMutableArray
}
mutating func add(_ other: MyArray) {
_arrayForWriting.addObjects(from: other._array as Array)
}
mutating func append(_ any: Any) {
_arrayForWriting.add(any)
}
var description: String {
return (_array as Array).description
}
}
我們通過get方法操作時叭爱,每次均復制 _array撮躁,這個結構體具有值語義了。但這樣的做法雖然有效买雾,但是當我們多次改變同一個變量時把曼,效率很低,因為每次操作漓穿,都會通過 get 方法去復制 _array得到 _arrayForWriting嗤军。比如如下操作:
var array = MyArray(NSArray(array: [1, 2, 3, 4]))
for index in 5...9 {
array.append(index)
}
每次 append 都會需要調用 get 方法去復制 _array 。如果我們能讓 _array 在沒有被共享之前晃危,對它進行原地變更就高效了叙赚。
寫時復制(高效方式)
上述問題說道 _array 沒有被共享之前,那就是我們要在它是唯一引用的時候進行原地修改僚饭,如果有別的變量也在強引用它震叮,那么我們就需要復制 _array 后去再修改它了。在 Swift 中鳍鸵,提供了 isKnownUniquelyReferenced 函數來檢查引用的唯一性苇瓣,沒有其他強應用將返回 true。由于對于 Objective-C 的類偿乖,它會直 接返回 false击罪,所以我們可以創(chuàng)建一個 Swift 類,來封裝 Objective-C 對象到 Swift類中汹想。
final class Box<T> {
var uniqueValue: T
init(_ value: T) {
self.uniqueValue = value
}
}
現在我們改造之前寫的 MyArray
struct MyArray {
private var _data: Box<NSMutableArray>
private var _dataForWriting: NSMutableArray {
mutating get {
if !isKnownUniquelyReferenced(&_data) {
_data = Box(_data.uniqueValue.mutableCopy() as! NSMutableArray)
print("Making a copy")
}
return _data.uniqueValue
}
}
init(_ array: NSArray) {
self._data = Box(array.mutableCopy() as! NSMutableArray)
}
mutating func append(_ other: MyArray) {
_dataForWriting.addObjects(from: other._data.uniqueValue as Array)
}
mutating func append(_ any: Any) {
_dataForWriting.add(any)
}
var description: String {
return (_data.uniqueValue as Array).description
}
}
現在我們可以測試一下:
static func testMyArray() {
var x = MyArray(NSArray(array: [1, 2, 3, 4]))
// 原地操作
x.append(5)
var y = x
print(x.description, y.description) // [1, 2, 3, 4, 5] [1, 2, 3, 4, 5]
y.append(6) // Making a copy
print(x.description, y.description) // [1, 2, 3, 4, 5] [1, 2, 3, 4, 5, 6]
}
原地操作不會發(fā)生復制外邓,而我們賦值給 y 后撤蚊,對應進行操作古掏,此時發(fā)生了復制。至此寫時復制已經完成
對于有的書上說:Array 和 Dictionary下標取值值元素有不同侦啸,通過下標操作其實一樣是會進行復制:
static func testTrap() {
let arrayForDict = MyArray(NSArray(array: [1, 2, 3, 4]))
var dict: [String: MyArray] = ["s": arrayForDict]
dict["s"]!.append(6) // Making a copy
let arrayForArray = MyArray(NSArray(array: [5, 6, 7, 8]))
var array = [arrayForArray]
array[0].append(9) // Making a copy
}