top of page

Mastering Swift's Development Blog

Follow Us On Twitter
  • Writer's pictureJon Hoffman

Value and Reference Types in Swift

Updated: Aug 25, 2022


In the infamous “Crusty Talk” at WWDC 2015, Dave Abrahams said “Don’t start with the class. Start with the Protocol”. In my humble opinion, I would not say that is totally the right approach however you do want to think about the protocol and understand where it fits into your design. Dave Abrahams later clarified that statement on Twitter by saying “Use value types, then if you need polymorphism, make them conform to a protocol. Avoid classes”. This clarification does ring truer to me and, once again in my humble opinion, is really the foundation of Protocol Oriented Design. So what is the difference between value and reference types? That is what this post will help to explain.


Basics of Value and Reference Types

Structs, enums and tuples are all value types which means that when we pass an instances of one within our applications, we are actually creating a new copy and passing the copy. Classes are reference types which means when we pass an instance of a class within our application, a reference to the original instance is passed.


To illustrate the difference between value and reference types, let's examine a real-world object: a book. If you asked to borrow a copy of our Mastering Swift 5.3, book we could either buy a new copy to give you or loan our copy.


If we bought you your own copy of the book, then any notes you make in the book would remain in your copy and would not be reflected in our original copy. This is how passing by value works with value types. Any changes that are made are not reflected back to the original instance of the type.


If we share our copy of the book, then any notes that are make in the book will stay in the book when you return it to us. This is how passing by reference works. Any changes that are made to the instance of the class remain.


The explanation we just used is pretty straight forward however as developers code examples show more than just words therefore let’s look at a real basic example before we get into some of the finer details of the differences between value and reference types.


Value vs Reference Types in Code


We will start off by creating a reference type (class) and a value type (struct) that we can use for demonstration purposes. Each of these types will have one property of the Int type.

class MyClass {
    var int = 0
}

struct MyStruct {
    var int = 0
}

Now let’s create a new instance of each type and then make copies of each instance as shown in the following code

var mc1 = MyClass()
var mc2 = mc1

var ms1 = MyStruct()
var ms2 = ms1

Currently the int property for each instance is equal to 0. Let’s change the int property for the copied instances, mc2 and ms2, to 42.

mc2.int = 42
ms2.int = 42

At this point what do you think is the value of the int property is for the original mc1 and the ms1 instances are? For the ms1 instance the value is still 0 because when we made the copy, the ms2 instance received a new copy of the ms1 instance, therefore no changes were reflected back to the original ms1 instance. For the mc1 instance, the value is now 42 because when we made the copy, the mc2 instance is just a reference to the original mc1 instance, therefore any change to either instance is reflected in both.


The following illustration shows this:



Now let’s look at the mutability of value and reference types when they are defined as constants.


Mutability of Value and Reference Types

The var and let keywords function differently when used to create instances of value or reference types. Let’s see what we mean by this. We will start off by creating an instance of the MyClass class and an instance of the MyStruct strut as constants like this.

let mc3 = MyClass()
let ms3 = MyStruct()

Now let’s change the int property of the mc3 instance to 42.

mc3.int = 42

All is good, if we run the code the int property of the mc3 instance is indeed 42. Now let’s try the same thing for the ms3 instance.

ms3.int = 42

If we run this code, we get the following error:

Cannot assign to property: 'ms3' is a 'let' constant

But you say that the int property is defined as a var therefore we should be able to change it, right? Well for value types, when an instance is defined as a constant the values of the instance must remain constant. What this means is that none of its properties, regardless of their declaration, can change.


The reason that we are able to change the property of the mc3 instance, when it is defined as a constant, is when a reference type is defined as a constant the reference itself must remain constant however the value can change.


As we can see with these examples, is that we are able to control mutability better with value types.


Performance of Value Types

Where Swift uses value types internally you may be wondering about the performance of large value types, such as arrays and other collection types, where we need to make a complete copy of an instance whenever we pass the instance in our code. As an example, what if we had an array with a million elements, what type of performance hit will our application take as we use it in our code.


For structures that have the possibility of becoming very large, Apple has implemented copy-on-write. Copy-on-write is a resource management technique that prevents new copies of data structures from be created unless there is a change in the data structure. How copy-on-write works and how we can implement it ourselves is covered in our Implementing Copy-On-Write post.


When to Use Value Types

Apple has recommended that we use value types and has said that one of the primary reasons for this is the ability to more easily reason about your code. If you always receive a unique copy of an instance, then you can trust that no other part of your code is changing the data. This is especially important for multi-thread environments where different threads can alter the data while other threads are trying to access it.


We would want to use value types when:


Comparing instance’s data make sense with ==: When comparing two instances of a value type, the comparison is whether the data within the instances are the same.

We want unique copies of the instances: We are not looking for a shared mutable state between instances of our type.

Instance will be share across multiple threads: We are in a multi-threaded environment, and we will be sharing instances of our type across the threads.


When to Use Reference Types

Although Apple has recommended that we prefer value types and they do have a number of benefits, there are still times that we should use reference types.


We would use reference types when:


Comparing instance’s identity makes sense with ==: When comparing two instances we wish to check that they are referencing the same instance.

When you need to create shared, mutable state: For some types, like recursive data types, we need a shared mutable state.


Conclusion

If you are unsure what type you should be using, then we would recommend defaulting to value types as Apple has also recommened. It is pretty trivial to move to a reference type later if you find you need too. In our experience, it is pretty rare that we actually use reference types and there needs to be a pretty compelling reason for it.


Consider that Swift itself uses almost exclusively value types. If Apple can build almost the entire language on value types, that says something. But in the end, we need to be using the right tool for the right job.

masteringSwift.jpeg

Mastering Swift 5.3

The sixth edition of this bestselling book, updated to cover through version 5.3 of the Swift programming language

Amazon

Packt

pop.jpeg

Protocol Oriented Programming

Embrace the Protocol-Oriented design paradigm, for better code maintainability and increased performance, with Swift.

Amazon

Packt

bottom of page