Chapters

Hide chapters

Swift Apprentice

Section III: Building Your Own Types

Section 3: 8 chapters
Show chapters Hide chapters

Section IV: Advanced Topics

Section 4: 10 chapters
Show chapters Hide chapters

24. Value Types & Reference Types
Written by Alexis Gallagher

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

Swift supports two kinds of types: value types and reference types. Structs and enums are value types, while classes and functions are reference types. These types differ in their behavior. The behavior you’ve come to expect from value types is the result of value semantics. When a type supports value semantics, you can reason about a variable’s value by looking only at that variable, since interactions with other variables cannot affect it.

The type guarantees the independence of variables, which rules out a large class of bugs. This is why most Swift standard library types support value semantics, why many Cocoa types are imported to offer value semantics, and why you should use value semantics when appropriate. That said, value semantics are not always the appropriate choice, and they can require some subtle handling to support correctly.

This chapter will define value semantics, show how to test for them, and explain when they’re suitable. You’ll learn how to build types with value semantics using value types, reference types, or some mix of the two. You’ll learn how a deft mixed type can offer the best of both worlds, with the simple interface of value semantics and the efficiency of reference types under the hood.

Value types vs. reference types

Value and reference types differ in their assignment behavior, which is just a name for what Swift does whenever you assign a value to a variable. Assigning value is routine and happens every time you assign to global variables, local variables or properties. You also assign whenever you call a function, effectively assigning a value to the function’s parameter.

Reference types

Reference types use assign-by-reference. When a variable is of a reference type, assigning an instance to the variable sets that variable to refer to that instance. If another variable was already referring to that instance, then both of those variables post-assignment now refer to the same 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!

Value types

Value types, however, use assign-by-copy. Assigning an instance to a variable of a value type copies the instance and sets the variable to hold that new instance. So after every assignment, a variable holds an instance which it owns all to itself.

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)

Defining value semantics

What’s nice about 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?

When to prefer value semantics

When should you design a type to support value semantics? While they are convenient, whether value semantics are appropriate depends on what the type is supposed to model.

Implementing value semantics

Now assume you do want value semantics. If you’re defining a type, how do you enforce it? The approach depends on the details of the type. In this section, you will consider the various cases one by one.

Case 1: Primitive value types

Primitive value types like Int support value semantics automatically. This 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. That is, the instance defines its own value independently of any other instance, so no other instance could affect its value.

Case 2: Composite value types

Composite value types, for example struct or enum, follow a simple rule: A struct supports value semantics if all its stored properties support value semantics.

Case 3: Reference types

Reference types can also have value semantics.

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

Case 4: value types containing mutable reference types

The final case is mixed types: value types that contain mutable reference types. This is the subtlest case but perhaps the most valuable. It allows combining the simple programming model of value types with the efficiency benefits of reference types.

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!

Copy-on-write to the rescue

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 access level of the setters 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)
      }
    }
  }
}

Sidebar: property wrappers

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 would 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
  // ...
}

Recipes for value semantics

To summarize, here is the recipe for determining if a type has value semantics or how to define your own such type: For a reference type (a class):

Challenges

Before moving on, here are some challenges to test your knowledge of value types and reference types. It is best if you try to solve them yourself, but solutions are available if you get stuck. These came with the download or are available at the printed book’s source code link listed in the introduction.

Challenge 1: Image with value semantics

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 use of memory in the case where 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

Challenge 2: Enhancing UIImage

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

Challenge 3: Generic property wrapper for CopyOnWrite

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 own 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
  }
}

Challenge 4: Implement @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 then it already meets these requirements, and it suffices to return `self`. But in this case, there's no point to using the `@ValueSemantic` property wrapper. */

  func deepCopy() -> Self
}

Challenge 5: Determining if a type has value semantics

Consider the test snippet used to determine if a type has value semantics. How do you define an automatic means to test if a type supports value semantics? If I handed you a type, could you know for sure if it offers value semantics? What if you could not see its implementation? Could the compiler be expected to know?

Key points

Where to go from here?

The best place to explore advanced implementations of value semantic types is in the Swift standard library, which relies on these optimizations extensively.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2023 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now