美文网首页iOS-SwiftSwift学徒
Swift中值类型内嵌套可变引用类型时值语义的实现

Swift中值类型内嵌套可变引用类型时值语义的实现

作者: 万恶胖为首 | 来源:发表于2017-01-21 10:36 被阅读24次

原文链接: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.bucketartPlan.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),获取进一步的讨论与彩蛋

Paste_Image.png

相关文章

网友评论

    本文标题:Swift中值类型内嵌套可变引用类型时值语义的实现

    本文链接:https://www.haomeiwen.com/subject/jtggbttx.html