Back to blog

Value Types vs. Reference Types In Swift

Aasif Khan
By Aasif Khan | December 28, 2021 10:30 pm  | 5-min read

In the Swift programming language, some types are value types and others are reference types. What’s the difference? And how do these types affect practical iOS development?

In this tutorial, you’ll learn:

  • What a value type and a reference type is
  • The difference between value types and reference types
  • Why that’s important to understand, in practical iOS development
  • When it’s best to use value types or reference types

Value Types In Swift

Let’s do a quick refresher of what a type exactly is. You already know that every variable, constant and property in Swift has a type. An integer number has type Int, a string of text has type String, and so on. You can assign a value of a certain type to a constant, for example, like this:

let age:Int = 42

In the above example, the value 42 of type Int is assigned to a constant called age. The constant is annotated with the Int type – an integer. The Int type itself is a struct.

We can distinguish between many different types, such as enums, structs, optionals, closures, tuples, classes, and function types. In short, in programming we save things, and those things have different names and types – so they’re easier to keep apart.

In this tutorial, we’re discussing a particular aspect of types: whether a type is a value type or a reference type. This affects the code you write, so it’s good to know the difference!

The simple explanation is that value types keep a unique copy of their data, whereas reference types share a copy of the data. Let’s find out what that means exactly!

var a = 99
var b = a
b += 1

print(a) // 99
print(b) // 100

The above code works exactly like you’d expect. Here’s what happens:

  • First, a value 99 is assigned to the constant a. The type of this constant is Int – an integer number, and a value type.
  • Then, a is assigned to a new constant b. The value is copied, and both a and b now keep a unique copy of the value 99. (This is important!)
  • Then, 1 is added to b, so the constant b now has value 100. The constant a is still 99.
  • Finally, both values are printed out.

What’s most important here is that the value of constant a is copied to constant b, when assigning a to b. This effectively creates a second copy of the value 99.

This assertion is proven by the fact that the value of a doesn’t change when 1 is added to b (line 3). We can clearly see that a and b are separate, unique values. Assigning a to b (line 2) effectively creates a copy of the data. This is the hallmark of a value type!

In Swift, the following types are value types:

  • Structs, such as Int, String, Double and Bool
  • Arrays, dictionaries and sets
  • Enumerations and tuples

It’s safe to assume that any fundamental (or “primitive”) type is a value type, such as strings and integers. Similarly, constructs such as structs, enumerations and tuples are all value types.

The Swift programming language favors value types over reference types, for a few reasons. It’s easier to reason about code when value types are the default. A value type can’t be changed by another thread or process as you’re working with it, and that makes your code less error-prone.

The smart developer will point out here that Swift doesn’t just blindly “copy” a value type any time you assign it. And you’d be right! Swift features a number of optimizations, including only copying a value if it actually changes. Otherwise you’d end up with two identical, unnecessary copies of data. The concept of value types vs. reference types is still valid though, because they aren’t affected by Swift’s optimizations.

Reference Types In Swift

So far so good, right? Value types are copied and keep a unique copy of the data. When you change one, the other doesn’t change too. So, what’s the problem?

The problem is that reference types work differently. A reference type keeps a reference to a shared instance whenever it’s assigned to a new variable, constant or property. And that can lead to all sorts of trouble!

Here, check this out:
class Car {
var speed = 0

let racecar = Car()
racecar.speed = 250

let bus = racecar
bus.speed = 40

print(racecar.speed) // 40
print(bus.speed) // 40

What’s going on!? Why are both the racecar.speed and bus.speed values 40? That’s the magic of reference types…

Here’s what’s going on in the code:

  • At the top, we’re defining a class named Car with one property called speed of type Int. It’s initially set to 0.
  • Then, we’re creating an instance of Car and assign it to constant racecar. We then set the speed property of racecar to 250.
  • Then, we’re assigning racecar to a new constant called bus. We also set the speed property of bus to 40. (After all, a bus is slower than a racecar.)
  • Finally, the speed property of both racecar and bus are printed out.

Now, check yourself: did you expect racecar.speed to be 250 or 40? If you’ve followed the previous example, it would be logical to expect that when racecar is assigned to bus, a unique copy of the Car instance would be created. But that’s not what’s happening here!

The Car type is a reference type, because it’s a class. Unlike value types, reference types aren’t copied when they’re assigned to a new variable, constant or property. Instead, the reference to the instance is copied. This reference points to the same instance, which means that both constants point to the same instance. You could say that both constants share the same instance.

In Swift, the following types are reference types:

  • Classes
  • Functions
  • Closures

You can see this clearly in the above example. The Car type is a class. When racecar is assigned to bus, the Car instance isn’t copied. Instead, the reference to this Car instance is copied to bus. Both racecar and bus now refer to the same instance! And so, if you change one, the other changes too…

What’s a reference? It’s easiest to think about a “reference” as an address. A variable, such as racecar, points to a memory address that contains the data of a Car instance. Much like how your home address “points to” your house!

Value Types vs. Reference Types

The definition of value types vs. reference types is:

  • Value type: each instance keeps a unique copy of its data. A copy of an instance is created when it’s assigned to a variable, constant or property.
  • Reference type: each instance shares the same copy of the data. A reference to one instance is created when it’s assigned to a variable, constant or property.

It’s easiest to remember the difference by thinking of the word “reference”. For a value type, the value is copied, and for a reference type, the reference is copied.

How do you know if a type is a value type or a reference type? It’s easiest to remember as: “In Swift, everything is a value type, unless it’s a class, function or closure.”

What if a reference type contains value types, and vice versa? In general, it’s perfectly OK if a reference type (i.e. a Person class) contains value types (i.e. a .name string). What’s more complicated, is when a value types contains reference types – for example, one or more Invoice objects that reference the same Person object. In short, it’s easiest to avoid this. If you must, implement your own deep copy method for the reference type, or find a good way to compare equality between two reference type objects.

Mutability With “let” And “var”

If you look closely at the previous example, you’ll see something odd. Notice that racecar is defined with let, so it’s a constant. Constants can’t change, they’re immutable by design. So how are we able to change it?

let and var work differently for value types and reference types. Here’s the gist of it:

  • For reference types, the reference itself needs to remain constant. So you can change the instance, like its properties, but you can’t change the reference.
  • For value types, the value itself needs to remain constant. So you can’t change the value, or any of its properties. The value is immutable.

You can infer from this that it’s much easier to control mutability with let and value types. Once a constant is declared, and it’s a value type, you can be 100% certain that the data will not and cannot change.

You can always change properties of a reference type, even when it’s declared with let. This makes it harder to reason about your code, because the immutability of a reference type relies on your implementation of it.

In Practical iOS Development

Before we can understand how value types vs. reference types affects practical iOS development, we’ll have to consider scenarios in which they matter.

  • When you use APIs, you need to follow the practices of that API, including which types are value types and reference types
  • When you code your own types, you need to decide if a type should be a value type or reference type

Following and using APIs

In the first scenario, working with value types vs. reference types is fairly easy. You’ll just have to follow along with the API. For example, in Swift, most simple types are value types. And in working with something like Realm, you’ll see that persisted objects are value types (that reference the actual database data).

You may wonder when exactly you’re assigning one variable to another, in a var b = a like fashion, in practical iOS development. It happens much more often than you might think! Every time you assign a variable or constant to another object’s property, for example.

A special scenario happens when working with Objective-C. As you may know, many types in Objective-C are bridged to Swift. For example, the class NSString is bridged to the struct String in Swift. This bridging is implicit, so even though Objective-C only has reference types, the bridged type in Swift is still a value type. Unfortunately, many other Objective-C APIs derive from the class NSObject and are therefore also reference types in Swift.

Creating your own types and APIs

But what if you’re coding your own classes, structs, enums and tuples? When should you opt for a value type, like a struct, and when for a reference type, like a class?

You can best choose between value types vs. reference types based on:

  1. How you compare if two objects are equal or identical
  2. Whether the type should have a shared or independent state

Determining equality or identicality

Consider that you’re coding a DollarBill type. Should this type be a value type, such as a struct, or a reference type, such as a class?

To decide, let’s first figure out how we’re going to determine that two dollar bills are the same. We can do that in two ways:

  • Two dollar bills are the same if they have the same monetary value, such as $20. In code, that’s expressed as isEqual = a.value == b.value, with the equality operator.
  • Two dollar bills are the same if they refer to the same physical, unique dollar bill. In code, that’s expressed as isEqual = a === b, with the identicality operator.

It doesn’t make sense to copy dollar bills, so it’s smart to choose a reference type here, such as a class. That way we can be 100% sure that any reference to a dollar bill refers to the actual bill. When the dollar bill is passed around in your code, you can be certain that no additional copies are created. They’re reference types!

Shared or independent state

Let’s take a look at another example. Consider that we’re creating an accounting system, with Invoice and Company types. Every invoice has an associated company, to which the invoice is sent. Should Invoice and Company be value types or reference types?

First off, it’s worth noting here that every invoice is likely to have a sequence number. Most companies use sequential numbering of invoices, so no invoice can go missing. Two invoices are the same if their IDs or invoice numbers are the same. In that case, it doesn’t matter much if you opt for a value or reference type.

But what about Company? On the one hand, it’s smart to have multiple invoices refer to the same company. When the company changes its name or address, you can just update one Company object, and every invoice updates automatically.

On the other hand, invoices should be immutable once issued. Otherwise you could just change the invoice amount and commit tax fraud. A company may change its address, but that doesn’t mean that past invoices should be reissued to the new address.

In this scenario, Invoice and Company instances don’t share the same state. The same company that’s associated with two invoices, are really two different companies from the invoice’s perspective. They have independent states – changing one doesn’t change the other. They’re value types!

Quick Tip: Use a “resistive” approach. Make any type you need a value type by default, a struct for example, just like Swift itself. Then change the struct to class if and when you need to. This change is trivial, and easier than going from class to struct. And as always, don’t shy away from refactoring your code!

Further Reading

It sounds so simple, right? Value types copy values, and reference types copy references. But what it means for your code – that’s much more complex! Whatever you do, take the time to play around with value types and reference types, and spend plenty of time planning and refactoring your app’s architecture.

Here’s what you learned in this tutorial:

  • What a value type and a reference type is
  • The difference between value types and reference types
  • Why that’s important to understand, in practical iOS development
  • When it’s best to use value types or reference types

Aasif Khan

Head of SEO at Appy Pie

App Builder

Most Popular Posts