寫時(shí)復(fù)制
在swift中,像Array、Dictionary、Set等集合類型都是通過(guò)寫時(shí)復(fù)制(copy-on-write)技術(shù)實(shí)現(xiàn)的。
例如一個(gè)整形數(shù)組
let x = [1,2,3,4]
var y = x
創(chuàng)建新變量 y,把 x 賦值給 y 會(huì)發(fā)生復(fù)制,現(xiàn)在x和y都是獨(dú)立的結(jié)構(gòu)體。然后這些Array 結(jié)構(gòu)體含有指向某個(gè)內(nèi)存的引用。這個(gè)內(nèi)存就是數(shù)組元素存儲(chǔ)在堆上的位置。我們可以通過(guò) swift Array 的
@inlinable public func withUnsafeBufferPointer<R>(_ body: (UnsafeBufferPointer<Element>) throws -> R) rethrows -> R
來(lái)獲取 Array 元素存儲(chǔ)的地址指針,我們可以看出數(shù)組x和y存儲(chǔ)地址和元素確實(shí)都是相同的
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]
然后我們對(duì)數(shù)組 y 進(jìn)行操作:
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]
通過(guò)以上代碼可以看出,當(dāng)y引用的存儲(chǔ)要改變時(shí),存儲(chǔ)進(jìn)行了復(fù)制。這就是所謂的"寫時(shí)復(fù)制"。
但是作為一個(gè)結(jié)構(gòu)體的作者,你不能免費(fèi)獲得這種特性,你需要自己進(jìn)行實(shí)現(xiàn)。當(dāng)結(jié)構(gòu)類型內(nèi)部含有一個(gè)或者多個(gè)可變引用,同時(shí)想要保持值語(yǔ)義,并且不必要的復(fù)制,為你的類型實(shí)現(xiàn)寫時(shí)復(fù)制是有意義的。
接下來(lái),我們用 NSMutableArray 作為內(nèi)部引用類型來(lái)實(shí)現(xiàn) swift 中的值類型的 Array 結(jié)構(gòu)體。
寫實(shí)復(fù)制(昂貴模式)
我們首先將 _array 標(biāo)記為結(jié)構(gòu)體的私有屬性。我們不再直接變更 _array,而
是通過(guò)一個(gè)計(jì)算屬性 _arrayForWriting 來(lái)訪問(wèn)它。這個(gè)計(jì)算屬性總是會(huì)復(fù)制 _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
}
}
我們通過(guò)get方法操作時(shí),每次均復(fù)制 _array,這個(gè)結(jié)構(gòu)體具有值語(yǔ)義了。但這樣的做法雖然有效,但是當(dāng)我們多次改變同一個(gè)變量時(shí),效率很低,因?yàn)槊看尾僮鳎紩?huì)通過(guò) get 方法去復(fù)制 _array得到 _arrayForWriting。比如如下操作:
var array = MyArray(NSArray(array: [1, 2, 3, 4]))
for index in 5...9 {
array.append(index)
}
每次 append 都會(huì)需要調(diào)用 get 方法去復(fù)制 _array 。如果我們能讓 _array 在沒(méi)有被共享之前,對(duì)它進(jìn)行原地變更就高效了。
寫時(shí)復(fù)制(高效方式)
上述問(wèn)題說(shuō)道 _array 沒(méi)有被共享之前,那就是我們要在它是唯一引用的時(shí)候進(jìn)行原地修改,如果有別的變量也在強(qiáng)引用它,那么我們就需要復(fù)制 _array 后去再修改它了。在 Swift 中,提供了 isKnownUniquelyReferenced 函數(shù)來(lái)檢查引用的唯一性,沒(méi)有其他強(qiáng)應(yīng)用將返回 true。由于對(duì)于 Objective-C 的類,它會(huì)直 接返回 false,所以我們可以創(chuàng)建一個(gè) Swift 類,來(lái)封裝 Objective-C 對(duì)象到 Swift類中。
final class Box<T> {
var uniqueValue: T
init(_ value: T) {
self.uniqueValue = value
}
}
現(xiàn)在我們改造之前寫的 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
}
}
現(xiàn)在我們可以測(cè)試一下:
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]
}
原地操作不會(huì)發(fā)生復(fù)制,而我們賦值給 y 后,對(duì)應(yīng)進(jìn)行操作,此時(shí)發(fā)生了復(fù)制。至此寫時(shí)復(fù)制已經(jīng)完成
對(duì)于有的書上說(shuō):Array 和 Dictionary下標(biāo)取值值元素有不同,通過(guò)下標(biāo)操作其實(shí)一樣是會(huì)進(jìn)行復(fù)制:
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
}