寫時復(fù)制
和Objective-C不同陶珠,在Swift中畜隶,Array犯犁、Dictionary属愤、Set這樣的集合不再是引用類型而是值類型了,這意味著酸役,每次傳遞不再是傳遞指針而是一個Copy后的值住诸,但是如果每次都要Copy一次的話就會太浪費性能,所以這時候就要用到一個寫時復(fù)制(copy-or-write)的技術(shù)涣澡。
var x = [1, 2, 3]
var y = x
x.append(5)
y.removeLast()
x // [1, 2, 3, 5]
y // [1, 2]
在內(nèi)部只壳,這些Array的結(jié)構(gòu)體含有指向某個內(nèi)存的引用。這個內(nèi)存就是數(shù)組中元素所存儲的位置暑塑。兩個數(shù)組的引用指向的是內(nèi)存中同一個位置,這兩個數(shù)據(jù)共享了它們的存儲部分锅必。當我們改變 x 的時候事格,這個共享會被檢測到,內(nèi)存將會被復(fù)制搞隐。所以說驹愚,復(fù)制操作只會則必要的時候發(fā)生。
這種行為被稱為寫時復(fù)制劣纲。它的工作方式是逢捺,每當數(shù)組被改變,它首先檢查它對存儲緩沖區(qū)的引用是否是唯一癞季,或者說劫瞳,檢查數(shù)組本身是不是這塊緩沖區(qū)的唯一擁有者。如果是绷柒,那么緩沖區(qū)可以進行原地變更志于;也不會有復(fù)制被進行。如果緩沖區(qū)有一個以上的持有者废睦,那么數(shù)組就需要先進行復(fù)制伺绽,然后對復(fù)制的值進行變化,而保持其他的持有者不受影響。
實現(xiàn)寫時復(fù)制
使用 NSMutableData 作為內(nèi)部引用類型來實現(xiàn) Data 結(jié)構(gòu)體奈应。
struct MyData {
var _data: NSMutableData
var flag: String?
init(_ data: NSData) {
_data = data.mutableCopy() as! NSMutableData
}
}
extension MyData {
func append(_ byte: UInt8) {
var mutableByte = byte
_data.append(&mutableByte, length: 1)
}
}
let theData = NSData(base64Encoded: "wAEP/w==")!
var x = MyData(theData)
x.flag = "flag"
var y = x
x._data == y._data
y.flag = "new flag"
x.append(0x55)
print(x) // MyData(_data: <c0010fff 55>, flag: Optional("flag"))
print(y) // MyData(_data: <c0010fff 55>, flag: Optional("new flag"))
MyData雖然是一個結(jié)構(gòu)體澜掩,是一個值類型,對于值類型數(shù)據(jù)遵循寫時復(fù)制的特性杖挣,但是對于內(nèi)部 NSMutableData 這樣的引用類型肩榕,多個 MyData 的變量指向的還是同一個 NSMutableData 地址。所以我們要手動實現(xiàn) NSMutableData 的寫時復(fù)制
簡單的實現(xiàn)
struct MyData {
fileprivate var _data: NSMutableData
fileprivate var _dataForWriting: NSMutableData {
mutating get {
_data = _data.mutableCopy() as! NSMutableData
return _data
}
}
var flag: String?
init() {
_data = NSMutableData()
}
init(_ data: NSData) {
_data = data.mutableCopy() as! NSMutableData
}
}
extension MyData {
mutating func append(_ byte: UInt8) {
var mutableByte = byte
_dataForWriting.append(&mutableByte, length: 1)
}
}
不直接變更 _data程梦,通過一個 _dataForWriting 來訪問点把。每次都會復(fù)制 _data 并將該復(fù)制返回。當我們調(diào)用 append 時屿附,將會進行復(fù)制
let theData = NSData(base64Encoded: "wAEP/w==")!
var x = MyData(theData)
x.flag = "flag"
var y = x
x._data == y._data
y.flag = "new flag"
x.append(0x55)
print(x) // MyData(_data: <c0010fff 55>, flag: Optional("flag"))
print(y) // MyData(_data: <c0010fff>, flag: Optional("new flag"))
但是這樣有一個問題郎逃,多次 append 時,就會非常浪費挺份,因為每次都要 copy
高效的方式
我們可以通過判斷一個對象是否是唯一的引用褒翰,來決定是否需要對這個對象進行復(fù)制。如果它是唯一引用匀泊,那就直接修改對象优训,否則,需要在修改前創(chuàng)建該對象的復(fù)制各聘。
在 Swift 中揣非,通過 isKnownUniquelyReferenced 函數(shù)來檢查某個引用只有一個持有者。只有一個返回 true躲因,否則返回 false早敬。對于 OC 類,它會直接返回 false大脉,我們需要創(chuàng)建一個 Swift 的類來包裝 OC 類
final class Box<A> {
var unbox: A
init(_ value: A) {
self.unbox = value
}
}
var x = Box(NSMutableData())
isKnownUniquelyReferenced(&x) // true
var y = x
isKnownUniquelyReferenced(&y) // false
讓我們再寫一個循環(huán)
struct MyData {
fileprivate var _data: Box<NSMutableData>
fileprivate var _dataForWriting: NSMutableData {
mutating get {
if !isKnownUniquelyReferenced(&_data) {
_data = Box(_data.unbox.mutableCopy() as! NSMutableData)
print("Making a copy")
}
return _data.unbox
}
}
init() {
_data = Box(NSMutableData())
}
init(_ data: NSData) {
_data = Box(data.mutableCopy() as! NSMutableData)
}
}
extension MyData {
mutating func append(_ byte: UInt8) {
var mutableByte = byte
_dataForWriting.append(&mutableByte, length: 1)
}
}
var bytes = MyData()
var copy = bytes
for byte in 0..<5 as CountableRange<UInt8> {
print("Appending 0x\(String(byte, radix: 16))")
bytes.append(byte)
}
print(bytes)
print(copy)
/*
Appending 0x0
Making a copy
Appending 0x1
Appending 0x2
Appending 0x3
Appending 0x4
MyData(_data: __lldb_expr_26.Box<__C.NSMutableData>)
MyData(_data: __lldb_expr_26.Box<__C.NSMutableData>)
*/
可以看到當?shù)谝淮?append 的時候搞监,拷貝了一份引用,之后因為新拷貝的引用是惟一的镰矿,就沒有進行復(fù)制操作