Reference vs. Value Types in Swift

Learn the subtle, but important, differences between reference and value types in Swift by working through a real-world problem. By Adam Rush.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

When to Use a Value Type

Here are three situations in which using a value type is the best choice.

Use a value type when comparing instance data with == makes sense.

I know what you’re thinking. Of course! You want every object to be comparable, right? But, you need to consider whether the data should be comparable. Consider the following implementation of a point:

struct Point: CustomStringConvertible {
  var x: Float
  var y: Float

  var description: String {
    return "{x: \(x), y: \(y)}"
  }
}

Does that mean two variables with the exact same x and y members are equal?

let point1 = Point(x: 2, y: 3)
let point2 = Point(x: 2, y: 3)

Yes. It’s clear that you should consider two Points with the same internal values to be equal. The memory location of those values doesn’t matter. Your concern is the values themselves.

To make your Point comparable, you’d need to conform to the Equatable protocol, which is good practice for all value types. This protocol defines only one function which you must implement in order to compare two instances of the object.

This means that the == operator must have the following characteristics:

  • Reflexive: x == x is true
  • Symmetric: if x == y then y == x
  • Transitive: if x == y and y == z then x == z

Here’s a sample implementation of == for your Point:

extension Point: Equatable { }
func ==(lhs: Point, rhs: Point) -> Bool {
  return lhs.x == rhs.x && lhs.y == rhs.y
}

Use a value type when copies should have independent state.

Taking your Point example a little further, consider the following two Shape instances with their centers as two initially equivalent Points:

struct Shape {
  var center: Point
}

let initialPoint = Point(x: 0, y: 0)
let circle = Shape(center: initialPoint)
var square = Shape(center: initialPoint)

What would happen if you altered the center point of one of the shapes?

square.center.x = 5   // {x: 5.0, y: 0.0}
circle.center         // {x: 0.0, y: 0.0}

Each Shape needs its own copy of a Point so you can maintain its state independent of the others. Can you imagine the chaos of all shapes sharing the same copy of a center Point?

Use a value type when the code will use this data across multiple threads.

Value types allow you to get a unique, copied instance of data that you can trust no other part of your app (such as another thread) is changing. In a multi-threaded environment, this can be super useful and will prevent nasty bugs that are extremely hard to debug.

To make your data accessible from multiple threads and equal across threads, you’ll need to use a reference type and implement locking as well — no easy task!

If threads can uniquely own the data, using value types avoids potential conflict since each owner of the data holds a unique copy rather than a shared reference.

When to Use a Reference Type

Although value types are viable in a multitude of cases, reference types are still useful in many situations.

Use a reference type when comparing instance identity with === makes sense.

=== checks if two objects are exactly identical, right down to the memory address that stores the data.

To put this in real-world terms, consider the following: If your cubicle mate swaps one of your $20 bills with another legitimate $20 bill, you don’t really care, as you’re only concerned about the value of the object.

However, if someone stole the Magna Carta and created an identical parchment copy of the document in its place, that would matter greatly because the inherent identity of the document is not the same at all, and in this case, the identity matters.

You can use the same thought process when deciding whether to use reference types. There are very few times when you really care about the inherent identity — that is, the memory location — of the data. You usually just care about comparing the data values.

Use a reference type when you want to create a shared, mutable state.

Sometimes you want to store a piece of data as a single instance that multiple consumers can access and mutate.

A common example of an object with a shared, mutable state is a shared bank account. You might implement a basic representation of an account and person, the account holder, as follows:

class Account {
  var balance = 0.0
}

class Person {
  let account: Account

  init(_ account: Account) {
    self.account = account
  }
}

If any joint account holders add money to the account, all debit cards linked to the account should reflect the new balance:

let account = Account()

let person1 = Person(account)
let person2 = Person(account)

person2.account.balance += 100.0

person1.account.balance    // 100
person2.account.balance    // 100

Since Account is a class, each Person holds a reference to the account, and everything stays in sync.

Still Undecided?

If you’re not quite sure which mechanism applies to your situation, default to value types. You can always move to a class later with little effort.

Class? Struct? Consider Struct!

Consider, though, that Swift uses value types almost exclusively, which is mind-boggling when you consider that the situation in Objective-C is completely the reverse.

As a coding architect under the new Swift paradigm, you need to do a bit of initial planning as to how your data will be used. You can solve almost any situation with either value types or reference types. However, using them incorrectly could result in a ton of bugs and confusing code.

In all cases, common sense and a willingness to change your architecture when new requirements come up is the best approach. Challenge yourself to follow the Swift model. You just might produce some nicer code than you expected!

You can download a completed version of this playground at the top or bottom of the tutorial by clicking on the Download Materials button.

Mixing Value and Reference Types

You’ll often run into situations where reference types need to contain value types and vice versa. This can easily complicate the expected semantics of an object.

To see some of these complications, below is an example of each scenario.