原文链接:https://vernsu.github.io/2017/01/20/
标识:Swift学徒
简单的嵌套结构
当一个值类型内嵌套一个可变引用类型拷贝时,引用类型作为 property 是引用的,拷贝所得的实例中 peoperty 和原实例中的 property 是同一个。
举个例子:
enum Color{
case blue
case green
}
class Bucket {
var color: Color
init(color: Color) {
self.color = color
}
}
struct PaintingPlan // 值类型
{
// 可变引用类型
var bucket = Bucket(color:.blue)
}
var artPlan = PaintingPlan()
var housePlan = artPlan
我们改变 bucket 的 color 试试:
artPlan.bucket.color // => blue
housePlan.bucket.color = .green
artPlan.bucket.color // => green. oops!
这是一种内隐的结构共享,housePlan.bucket
和artPlan.bucket
指向的是同一个实例。导致 PaintingPlan
虽然是值类型,但是没有值语义。
我们直接改变 bucket 试试
artPlan.bucket.color // => blue
housePlan.bucket = Bucket(color:.green)
artPlan.bucket.color // => blue OK!
这个时候好像没有任何问题。因为进行拷贝操作时,我们拷贝了bucket的引用,上面这几行代码中,我们并没有对该引用实例做修改,而是直接将引用指向了另外一个实例。
//--TODO:更多内容参见:值类型与引用类型的简单嵌套
值语义的实现
修复值语义
首先,我们意识到,值语义的定义是相对于存取级别的。一个类型可能会对它的客户代码提供值语义,而对它内部的私有成员不提供值语义。
所以,保留混合类型值语义的技巧在于:让客户无法改变内部的引用类型。上面这个例子中,我们让可变引用类型私有,提供一个可以控制读写的接口。
struct PaintingPlan // 值类型
{
// 一个私有引用类型, for "deep storage"
private var bucket = Bucket(color: .blue)
// 一个伪装的值类型,使用了 deep storage
var bucketColor: Color {
get {
return bucket.color
}
set {
//注意,这里我们做了控制,让 bucket 只能指向了一个新的实例,而不能修改之前指向的实例,使得它拥有值语义。
bucket = Bucket(color: newValue)
}
}
}
var artPlan = PaintingPlan()
var housePlan = artPlan
housePlan.bucketColor = Color.green
artPlan.bucketColor // blue!
对该 struct 的客户来说,表现出来的就是值语义。引入了一个计算属性,使用 get 和 set 来控制私有的引用类型的存取级别。这个行为在苹果官方文档中被叫做 deep storage,也被叫做 indiect storage 或 backing storage。
当用户修改 bucketColor 时, setter 创建了一个不同的实例。
至此,值语义的实现算是解决了。
写时复制(COW)
这样做,产生了另一个结果:和简单的值类型相比,给 PaintingPlan 分配一个值时,不会在分配时立刻拷贝 backing storage 。实际上多个实例会共享一个 backing storage。 但是看上去好像每个实例拥有他们自己的 backing storage,因为一旦需要,就会私下创建他自己的独特的 backing storage。
这种模式被称作写时复制:因为系统只在当你需要修改变量的时候拷贝 backing storeage。
Swift 标准库中大量使用了这种技术。Swift中很多值类型并不是原始的值类型,但是混合类型提供了值语义。
这样做的好处在于提高性能。想象一个,如果 backing storeage 非常大,实例可以共用相同的 backing storeage, 这样可以节约拷贝占用的内存和计算力。
但是一旦你使用变量修改实例,为了不影响别的变量,系统会拷贝 backing storeage 。这样可以推迟内存占用和计算力消耗直到必须拷贝时。
这个例子还可以进一步的优化,详细介绍参考写时复制(COW)在 Swift 中的应用。
关注公众号(ID:SwiftBetter),获取进一步的讨论与彩蛋
![](https://img.haomeiwen.com/i525557/c91106b7278692a7.png)
网友评论