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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Reference vs. Value Types in Swift
30 mins
- Getting Started
- Reference Types vs. Value Types
- Reference Types
- Value Types
- Mutability
- What Type Does Swift Favor?
- Which to Use and When
- When to Use a Value Type
- When to Use a Reference Type
- Still Undecided?
- Mixing Value and Reference Types
- Reference Types Containing Value Type Properties
- Value Types Containing Reference Type Properties
- Getting Value Semantics From Mixed Types
- Copying References During Initialization
- Using Copy-on-Write Computed Properties
- Defensive Mutating Methods
- A More Efficient Copy-on-Write
- Where to Go From Here?
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 Point
s 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
theny == x
-
Transitive: if
x == y
andy == z
thenx == 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 Point
s:
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.
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.