写时复制
和Objective-C不同,在Swift中,Array、Dictionary、Set这样的集合不再是引用类型而是值类型了,这意味着,每次传递不再是传递指针而是一个Copy后的值,但是如果每次都要Copy一次的话就会太浪费性能,所以这时候就要用到一个写时复制(copy-or-write)的技术。
var x = [1, 2, 3]
var y = x
x.append(5)
y.removeLast()
x // [1, 2, 3, 5]
y // [1, 2]
在内部,这些Array的结构体含有指向某个内存的引用。这个内存就是数组中元素所存储的位置。两个数组的引用指向的是内存中同一个位置,这两个数据共享了它们的存储部分。当我们改变 x 的时候,这个共享会被检测到,内存将会被复制。所以说,复制操作只会则必要的时候发生。
这种行为被称为写时复制。它的工作方式是,每当数组被改变,它首先检查它对存储缓冲区的引用是否是唯一,或者说,检查数组本身是不是这块缓冲区的唯一拥有者。如果是,那么缓冲区可以进行原地变更;也不会有复制被进行。如果缓冲区有一个以上的持有者,那么数组就需要先进行复制,然后对复制的值进行变化,而保持其他的持有者不受影响。
实现写时复制
使用 NSMutableData 作为内部引用类型来实现 Data 结构体。
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虽然是一个结构体,是一个值类型,对于值类型数据遵循写时复制的特性,但是对于内部 NSMutableData 这样的引用类型,多个 MyData 的变量指向的还是同一个 NSMutableData 地址。所以我们要手动实现 NSMutableData 的写时复制
简单的实现
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 来访问。每次都会复制 _data 并将该复制返回。当我们调用 append 时,将会进行复制
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
高效的方式
我们可以通过判断一个对象是否是唯一的引用,来决定是否需要对这个对象进行复制。如果它是唯一引用,那就直接修改对象,否则,需要在修改前创建该对象的复制。
在 Swift 中,通过 isKnownUniquelyReferenced 函数来检查某个引用只有一个持有者。只有一个返回 true,否则返回 false。对于 OC 类,它会直接返回 false,我们需要创建一个 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
让我们再写一个循环
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>)
*/
可以看到当第一次 append 的时候,拷贝了一份引用,之后因为新拷贝的引用是惟一的,就没有进行复制操作
网友评论