在这里,我们要讲讲值类型和写时复制。在 swift 的标准库中,所有的集合类型都使用了写时复制。我们在本篇文章中看一下写时复制如何工作的,并且如何实现它。

引用类型

使用 swift 的 DataNSMutableData 作对比

var sampleBytes: [UInt8] = [0x0b, 0xad, 0xf0, 0x0d]
let nsData = NSMutableData(bytes: sampleBytes, length: sampleBytes.count)

在这里,我们使用了 let 来修饰 nsData 变量,但是因为 NSMutableData 是一个引用类型,swift 的 let/var 关键字不能控制它。对于引用类型,let 只能保证 nsData 不被指向其他实例,但是我们可以修改他:

nsData.append(sampleBytes, length: sampleBytes.count)

因为我们操作的是一个对象,在以下例子中,两个对象都会发生改变:

// 两者都指向同一个对象,所以都改变了
let nsOtherData = nsData
nsData.append(sampleBytes, length: sampleBytes.count)

如果不希望 nsOtherData 也随之改变,可以使用 mutableCopy

// 这样 nsOtherData 就不会改变
let nsOtherData = nsData.mutableCopy() as! NSMutableData
nsData.append(sampleBytes, length: sampleBytes.count)

值类型

现在我们看一下 Data 类型,这是一个结构体

let data = Data(bytes: sampleBytes, count: sampleBytes.count)

这里我们使用了 let 修饰,这样就不能修改 data 的值,除非将他设置为 var。并且将 data 赋值给别的变量,修改变量也不会影响 data。

值类型和引用类型之间的差异在于,当你将一个值类型赋值给别的变量或者作为函数参数时,只是对值进行了赋值。但是将引用类型分配给其他变量时,只会创建指向内存中同一个对象的第二个引用。

当我们创建一个副本时,结构体被逐个复制。但是这不意味着 Data 的值直接被复制过去,因为 Data 有一个内部的内存引用。当结构体被复制时,只是引用被复制给新值。只有当复制的变量值改变时,才会将值复制过去。

实现写时复制

在这里我们将实现一个很简单的结构体版本,来更好的理解写时复制

struct MyData {
var data = NSMutableData() mutating func append(_ bytes: [UInt8]) {
data.append(bytes, length: bytes.count)
}
}

接下来看看结果如何

var data = MyData()
var copy = data
data.append(sampleBytes)

Copy 还是被改变了,这是因为在结构体复制时,将引用复制了过去,这个引用指向了实际的值,所以 copy 还是被改变,以下代码就可以修复这个问题

struct MyData {
var data = NSMutableData() mutating func append(_ bytes: [UInt8]) {
print("making a copy")
data = data.mutableCopy() as! NSMutableData
data.append(bytes, length: bytes.count)
}
}

现在 copy 的值不会被改变了,接下来重构一下结构体,更加优雅点:

struct MyData {
var data = NSMutableData()
var dataForWriting: NSMutableData {
mutating get {
print("making a copy")
data = data.mutableCopy() as! NSMutableData
return data
}
} mutating func append(_ bytes: [UInt8]) {
dataForWriting.append(bytes, length: bytes.count)
}
}

让写时复制更高效

目前的做法是很幼稚的,我们会在每次 append 的时候都去复制,而不去管我们是这个对象的唯一拥有者,例如以下代码:

for _ in 0..<10 {
data.append(sampleBytes)
}
// This prints:
// making a copy
// making a copy
// making a copy
// making a copy
// making a copy
// making a copy
// making a copy
// making a copy
// making a copy
// making a copy

以上代码在理想情况下应该只 copy 一次,想这样做的话也很简单,使用 isKnownUniquelyReferenced 方法,这个方法可以判断传入的参数是否已经有一个强引用

struct MyData {
var dataForWriting: NSMutableData {
mutating get {
if isKnownUniquelyReferenced(&data) {
return data
}
print("making a copy")
data = data.mutableCopy() as! NSMutableData
return data
}
}
}

但是上面的代码还是没有起效的,因为 isKnownUniquelyReferenced 只适用于 swift 对象,现在我们来将 NSMutableData 包装为 swift 对象:

final class Box<A> {
// 使用这个常量
let unbox: A
init(_ value: A) {
unbox = value
}
}
struct MyData {
var data = Box(NSMutableData())
var dataForWriting: NSMutableData {
mutating get {
if isKnownUniquelyReferenced(&data) {
return data.unbox
}
print("making a copy")
data = Box(data.unbox.mutableCopy() as! NSMutableData)
return data.unbox
}
} mutating func append(_ bytes: [UInt8]) {
dataForWriting.append(bytes, length: bytes.count)
}
}

现在我们再去进行之前的遍历操作会发现只复制了一次,有点类似 lazy

写时复制也不是时时起效

(0..<10).reduce(data) { result, _ in
var copy = result
copy.append(sampleBytes)
return copy
}

上面的写法会 copy10次,所以写时复制也不是万能的,但是一般情况下不会出现这种问题。

上面 reduce 内部做了什么?

  • (0..<10) 代表了闭包会进行10次
  • 接受了一个初始值参数,并将这个初始值作为第一次遍历的 result 的值
  • 返回的 copy 作为下一次循环的 result 值

参考自 swift talk

作者:没阳光的午后
链接:http://www.jianshu.com/p/2adbcc8b6389
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

最新文章

  1. C#遍历文件夹下所有文件
  2. MySQL-Front 建表引发的一点小思考(数据表格模版)
  3. 时间同步出现ntpdate[1788]: the NTP socket is in use, exiting
  4. HTTP请求头详解
  5. fsimage 和 edits log
  6. android 开发 - 网络图片加载库 Fresco 的使用。
  7. 7.FPGA中的同步复位与异步复位
  8. Python 批量创建同文件名的特定后缀文件
  9. kettle实现文本文件数据抽取方法
  10. [转] LCA与Tarjan
  11. ###《Effective STL》--Chapter4
  12. 从本地上传整个目录到hdfs的java程序
  13. mysql一些使用技巧
  14. Windows下安装tesserocr
  15. C++设计模式——适配器模式
  16. 不得不提的volatile及指令重排序(happen-before)
  17. wpf UI 布局
  18. JAVA多线程之中断机制(如何处理中断?)
  19. Cocos Creator 对象池cc.NodePool的使用
  20. 自定义yum源

热门文章

  1. elasticsearch模板 template
  2. SQL学习--Select(一)TOP、派生表、连接、谓词
  3. 释放Win8.1 WinSxS冗余更新,微软Dism来解决
  4. AI:AI是什么?
  5. Matlab矩阵填充--Matlab interp2
  6. servlet_获取初始化参数
  7. JSP_内置对象_session
  8. centos7 删除libc.so.6 紧急救援
  9. mysql主主同步
  10. Python Shell 中敲击方向键显示「^[[C^[[D」,原因是什么?如何修复?