首页 iOS.& Swift Books Swift学徒

24
值类型& Reference Types 由Alexis Gallagher撰写

SWIFT支持两种类型:值类型和参考类型。 structs和枚举是值类型,而类和函数是引用类型,它们的行为不同。从价值类型所期望的行为是结果 价值语义。当类型支持值语义时,您可以通过仅查找该变量来推理变量的值,因为与其他变量的交互不能影响它。

值类型 保证变量的独立性,这已经排除了一大类错误。此安全性是为什么大多数SWIFT标准库类型值语义,为什么导入许多可可类型以提供语义值,以及为什么要在拨款时使用价值语义。价值语义并不总是正确的选择,它们可能需要一些微妙的处理来支持正确。

本章将定义价值语义,显示如何为其发短信,并在适合时解释。您将了解如何构建具有值语义类型值,引用类型或其中中的类型的类型。您将了解如何提供脏混合的类型可以提供最佳的世界,具有语义值的简单界面和引擎盖下的参考类型的效率。

值类型与引用类型

值类型和引用类型在不同的对话 赋值行为,只要将值分配给变量时,它只是一个威速的名称。分配值是例程,每次分配给全局变量,局部变量或属性时都会发生。您还可以在调用函数时分配,有效地将参数分配给函数的参数。

参考类型

参考类型使用 分配逐个引用。当变量是引用类型时,将实例分配给变量设置该变量以引用该实例。如果另一个变量已经指的是该实例,则两个变量赋值后都会引用 相同的 instance, like so:

struct Color: CustomStringConvertible {
  var red, green, blue: Double
  
  var description: String {
    "r: \(red) g: \(green) b: \(blue)"
  }
}

// Preset colors
extension Color {
  static var black = Color(red: 0, green: 0, blue: 0)
  static var white = Color(red: 1, green: 1, blue: 1)
  static var blue  = Color(red: 0, green: 0, blue: 1)
  static var green = Color(red: 0, green: 1, blue: 0)
  // more ...
}

// Paint bucket abstraction
class Bucket {
  var color: Color
  var isRefilled = false
  
  init(color: Color) {
    self.color = color
  }
  
  func refill() {
    isRefilled = true
  }
}
let azurePaint = Bucket(color: .blue)
let wallBluePaint = azurePaint
wallBluePaint.isRefilled // => false, initially
azurePaint.refill()
wallBluePaint.isRefilled // => true, unsurprisingly!

价值类型

但是,值类型使用 分配逐副本。指定实例的值类型的变量 副本 实例并设置变量以保存该新实例。因此,在每个赋值之后,一个变量包含一个实例,它自身拥有全部。

extension Color {
  mutating func darken() {
    red *= 0.9; green *= 0.9; blue *= 0.9
  }
}

var azure = Color.blue
var wallBlue = azure
azure  // r: 0.0 g: 0.0 b: 1.0
wallBlue.darken()
azure  // r: 0.0 g: 0.0 b: 1.0 (unaffected)

定义价值语义

What’s nice about primitive value types like Color or Int is not the assign-by-copy behavior itself, but rather the guarantee this behavior creates.

var x = MysteryType()
var y = x
exposeValue(x) // => initial value derived from x
// {code here which uses only y}
exposeValue(x) // => final value derived from x
// Q: are the initial and final values different?

何时更喜欢价值语义

您应该什么时候设计一种支持价值语义的类型?此选择取决于您的类型应该模拟的内容。

实现价值语义

现在假设您确实想要价值语义。如果您是定义类型,您如何强制执行它?该方法取决于类型的细节。在本节中,您将逐一考虑各种情况。

案例1:原始值类型

Primitive value types like Int support value semantics automatically. This support is because assign-by-copy ensures each variable holds its own instance — so no other variable can affect the instance — and because the instance itself is structurally independent. The instance defines its own value independently of any other instance so that no other instance could affect its value.

案例2:复合值类型

Composite value types other than class, like a tuple, struct or enum, support value semantics if all the stored components support value semantics.

案例3:参考类型

引用类型也可以有值语义。

var a = UIImage(named:"smile.jpg")
var b = a
computeValue(b) // => something
doSomething(a)
computeValue(b) // => same thing!

情况4:含有可变引用类型值类型

最后一个案例是混合类型:包含可变参考类型的值类型。这种情况是子列最有价值的。它可以允许提供简单的值语义编程模型,具有参考类型的效率优势。但它很容易无法这样做。

struct PaintingPlan { // a value type, containing ...
  // a value type
  var accent = Color.white
  // a mutable reference type
  var bucket = Bucket(color: .blue)
}
let artPlan = PaintingPlan()
let housePlan = artPlan
artPlan.bucket.color // => blue
// for house-painting only we fill the bucket with green paint
housePlan.bucket.color = Color.green
artPlan.bucket.color // => green. oops!

复制到救援写作

What’s the fix? The first step lies in recognizing that value semantics are defined relative to an access level. Value semantics depend on what changes you can make and see with a variable, which depends on the setters’ access level and mutating functions of the variable’s type. So a type may provide value semantics to all client code — for example, which can access internal or public members — while not providing value semantics to code that can access its private members.

struct PaintingPlan { // a value type, containing ...
  // a value type
  var accent = Color.white
  // a private reference type, for "deep storage"
  private var bucket = Bucket()

  // a pseudo-value type, using the deep storage
  var bucketColor: Color {
    get {
      bucket.color
    }
    set {
      bucket = Bucket(color: newValue)
    }
  }
}
struct PaintingPlan { // a value type, containing ...
  // ... as above ...

  // a computed property facade over deep storage
  // with copy-on-write and in-place mutation when possible
  var bucketColor: Color {
    get {
      bucket.color
    }
    set {
      if isKnownUniquelyReferenced(&bucket) {
        bucket.color = bucketColor
      } else {
        bucket = Bucket(color: newValue)
      }
    }
  }
}

侧边栏:物业包装器

As you can see above, the copy-on-write pattern is verbose. You need to define the private, stored reference-type property for the backing storage (the bucket), the computed property that preserves value semantics (the bucketColor), and the tricky copy-on-write logic itself in the getter and setter. If PaintingPlan contains dozens of such properties, this will get repetitive.

struct PaintingPlan {
  
  var accent = Color.white
  @CopyOnWriteColor var bucketColor = .blue
}
private var _bucketColor = CopyOnWriteColor(wrappedValue: .blue)

var bucketColor: Color {
  get { _bucketColor.wrappedValue }
  set { _bucketColor.wrappedValue = newValue }
}
@propertyWrapper
struct CopyOnWriteColor {

  init(wrappedValue: Color) {
    self.bucket = Bucket(color: wrappedValue)
  }

  private var bucket: Bucket

  var wrappedValue: Color {
    get {
      bucket.color
    }
    set {
      if isKnownUniquelyReferenced(&bucket) {
        bucket.color = newValue
      } else {
        bucket = Bucket(color:newValue)
      }
    }
  }
}
struct PaintingPlan {

  var accent = Color.white

  @CopyOnWriteColor var bucketColor = .blue
  @CopyOnWriteColor var bucketColorForDoor = .blue
  @CopyOnWriteColor var bucketColorForWalls = .blue
  // ...
}

价值语义的食谱

为了总结,这里是用于确定类型是否具有值语义的配方或如何定义自己的此类类型:

挑战

以下是在继续之前测试您对价值类型,参考类型和值语义的知识的一些挑战。最好尝试自己解决它们,但如果你被困,可以使用解决方案。这些随下载或在介绍中列出的印刷书的源代码链接中提供。

挑战1:图像的值语义

Build a new type, Image, that represents a simple image. It should also provide mutating functions that apply modifications to the image. Use copy-on-write to economize the use of memory when a user defines a large array of these identical images and doesn’t mutate any of them.

private class Pixels {
  let storageBuffer: UnsafeMutableBufferPointer<UInt8>

  init(size: Int, value: UInt8) {
    let p = UnsafeMutablePointer<UInt8>.allocate(capacity: size)
    storageBuffer = UnsafeMutableBufferPointer<UInt8>(start: p, count: size)
    storageBuffer.initialize(from: repeatElement(value, count: size))
  }

  init(pixels: Pixels) {
    let otherStorage = pixels.storageBuffer
    let p  = UnsafeMutablePointer<UInt8>.allocate(capacity: otherStorage.count)
    storageBuffer = UnsafeMutableBufferPointer<UInt8>(start: p, count: otherStorage.count)
    storageBuffer.initialize(from: otherStorage)
  }
  
  subscript(offset: Int) -> UInt8 {
    get {
      storageBuffer[offset]
    }
    set {
      storageBuffer[offset] = newValue
    }
  }
  
  deinit {
    storageBuffer.baseAddress!.deallocate(capacity: self.storageBuffer.count)
  }
}
var image1 = Image(width: 4, height: 4, value: 0)

// test setting and getting
image1[0,0] // -> 0 
image1[0,0] = 100
image1[0,0] // -> 100
image1[1,1] // -> 0

// copy
var image2 = image1
image2[0,0] // -> 100
image1[0,0] = 2
image1[0,0] // -> 2
image2[0,0] // -> 100 because of copy-on-write

var image3 = image2
image3.clear(with: 255)
image3[0,0] // -> 255
image2[0,0] // -> 100 thanks again, copy-on-write

挑战2:增强 UIImage

Pretend you’re Apple and want to modify UIImage to replace it with a value type with the mutating functions described above. Could you do make it backward compatible with code that uses the existing UIImage API?

挑战三:通用属性包装的写入时复制

Consider the property wrapper CopyOnWriteColor you defined in this chapter. It lets you wrap any variable of type Color, and it manages the sharing of an underlying storage type, Bucket, which owns a single Color instance. Thanks to structural sharing, multiple CopyOnWriteColor instances might share the same Bucket instance, thus sharing its Color instance, thus saving memory.

private class StorageBox<StoredValue> {
  var value: StoredValue
  
  init(_ value: StoredValue) {
    self.value = value
  }
}

挑战之四:公开从事@ValueSemantic

Using the following protocol DeepCopyable as a constraint, write the definition for this generic property wrapper type, @ValueSemantic, and use it in an example to verify that the wrapped properties have value semantics, even when they are wrapping an underlying type which does not. Use NSMutableString is an example of a non-value semantic type.

protocol DeepCopyable {
  /* Returns a _deep copy_ of the current instance.

   If `x` is a deep copy of `y`, then:
   - the instance `x` should have the same value as `y` (for some sensible definition of value -- _not_ just memory location or pointer equality!)
   - it should be impossible to do any operation on `x` that will modify the value of the instance `y`.

   If the conforming type is a reference type (or otherwise does not have value semantics), then the way to achieve a deep copy is by ensuring that `x` and `y` do not share any storage, do not contain any properties that share any storage, and so on..

   If the conforming type already has value semantics, it already meets these requirements, and it suffices to return `self`. But in this case, there's no point in using the `@ValueSemantic` property wrapper. */

  func deepCopy() -> Self
}

挑战5:如果一个类型值语义确定

考虑用于确定类型是否具有值语义的测试代码段。如何定义自动方法以测试类型是否支持价值语义?如果我递给你一种类型,你可以知道它是否提供价值语义?如果你看不到它的实施怎么办?编译器可以预期知道吗?

关键点

然后去哪儿?

探索价值语义类型的高级实现的最佳位置是Swift标准库,它依赖于这些优化。

有技术问题吗?要报告bug? 你可以问的问题和bug报告本书的作者在我们的官书论坛 这里.

有关于网上阅读体验反馈给份额? 如果您有关于UI,UX,突出显示或我们在线阅读器的其他功能的反馈,您可以将其发送到设计团队,其中表格如下所示:

© 2021 Razeware LLC

您正在阅读的自由,以显示为本章的部分 混淆了 文本。解锁这本书,以及我们整个书籍和视频目录,带有Raywenderlich.com的专业订阅。

现在解锁

为了突出或做笔记,你需要通过自己拥有这本书的订阅或能力。