Introduction To Swift Programming (Part 5): Value Types vs. Reference Types in Swift

A Practical Guide to Swift’s Data Handling Mechanisms

Hina Khan
Women in Technology
6 min readOct 26, 2023

--

Image By Author on Canva

In Swift, every piece of data can be categorized as either

• A value type
• A reference type

These distinctions influence how data is stored, copied, and modified within our code.

In this article, we’ll delve into the differences between value types and reference types, and why value types, particularly structs, have their unique advantages.

Value Types vs. Reference Types

🔍 𝗩𝗮𝗹𝘂𝗲 𝘁𝘆𝗽𝗲𝘀 are types that hold a value, such as Integers, Boolean, Strings, struct, enum, tuple, and arrays. Each instance keeps a unique copy of its data. Value types are passed by value, meaning that a copy of the value is passed to a method or function.

🔍 𝗥𝗲𝗳𝗲𝗿𝗲𝗻𝗰𝗲 𝘁𝘆𝗽𝗲𝘀, are the types that hold a reference to an object in memory such as classes, closure, and functions. Each instance shares a single copy of its data. Reference types are passed by reference, meaning that a reference to the object is passed to a method or function.

Understanding the Behaviour of Pass by Value and Pass by Reference

The below GIF can help explain ‘pass by reference’ and ‘pass by value’ more easily.

The cup, in this context, symbolises an object, while the tea it contains represents the data within that object.

Pass by Reference and Pass by Value

Pass by Reference: It’s similar to sharing one cup of tea with friends. If someone adds more tea to the cup, everyone gets more tea because they all use the same cup. In programming, this means that if you change something, it changes for everyone using that same thing, as they all share the same underlying object.

Pass by Value: Conversely, ‘pass by value’ is like getting a new, identical cup. When we pour tea into this new cup, it doesn’t affect the amount of tea in the original cup. They are two separate cups and the new cup is an independent copy, unaffected by changes made to it or other cups. In programming, this means making a copy of the original data to ensure that changes in one instance don’t affect the others.

“Pass by reference” means that the new variable adopts a memory address of an initial variable. Since both variables refer to the same memory address, changes in one variable will affect another one. In this instead of creating a copy of a variable, changes are made at the original memory address.

“Pass by value” means that the new variable only adopts an actual value of an initial variable. I.e., variables do not depend on each other and changes in one variable will not affect another one. A copy of variable is created and changes are not reflected in the original memory address.

Example to illustrate the concept of value type:

import UIKit

var a = 3 // 3
var b = a // 3
b = 5 // 5
a // 3

In this scenario, a and b are both integer variables, and a is initially assigned the value 3. Here we are dealing with value semantics. It means that what truly matters about a is its value, not its origin.

In this case, when we assign the value of a to b, we essentially create a copy of that value. Thus, a remains a value type with the value 3, and any changes to b do not affect the original variable a.

Now, let’s explore “pass by reference” with a class:

When working with a reference type like a class, creating an instance and assigning it to a new variable essentially means we’re creating a reference to the same object in memory. Any changes made to one instance will reflect in all copies of that instance.

For example, we have a class, “Numbers,” with a stored property n.

import UIKit

class Numbers {
var n: Int

init(_ n: Int) {
self.n = n
}
}

Now, when we create an instance of this class, let’s call it a, with a.n initially set to 3:

var a = Numbers(3) // Create an instance of "Numbers" with n = 3

Next, we copy this instance into another variable, b:

var b = a          // Copy instance 'a' to 'b'

Now, let’s set b.n to 5:

b.n = 5            // Modify 'b.n' to 5

What do you expect the value of ‘a.n’ to be???

a.n                // What do you expect the value of 'a.n' to be?

We’re essentially assigning a reference, not making a direct copy. So, in this case, both a and b point to the same underlying object in memory.

Therefore, In this case changing b.n to 5 also modifies a.n to 5 because they share the same reference.

When we work with a reference type, we’re essentially working with a reference to the same object in memory.

What will happen if Number is a struct???

Now, let’s take the same class and transform it into a struct:

import UIKit

struct Numbers {
var n: Int

init(_ n: Int) {
self.n = n
}
}

var a = Numbers(3) // Create an instance of "Numbers" with n = 3
var b = a // Copy instance 'a' to 'b'
b.n = 5 // Modify 'b.n' to 5

a.n // What do you expect the value of 'a.n' to be now?

By making this change and declaring “Numbers” as a struct, the behaviour of our code completely shifts.

This is because, all of a sudden, numbers is now a value type, by declaring it as a struct. When we work with a value type, a copy of the entire struct, including its internal values, is made. Thus changes to one instance won’t affect others.

In this case, a.n remains 3, even after modifying b.n to 5.

Why are value types valuable?

When deciding between value types and reference types in programming, it’s crucial to understand their distinct advantages, especially in Swift.

A common challenge when dealing with reference types is the passing of references throughout our code. Many components or functions may refer to the same object, which can lead to unintended consequences when modifications are made. For example, changing an object’s properties can impact other parts of the code that depend on that object. This can create challenging bugs and make our code harder to reason about.

One key benefit of value types, such as structs, on the other hand, provide a safer approach to working with data. When we use a struct, we know that we have our own copy of the data. This means that modifications to a struct won’t affect other parts of our code unintentionally. This level of isolation is particularly valuable in complex software systems.

Conclusion

Value types, especially structs, provide simplicity and data isolation, ensuring changes don’t ripple through our code unexpectedly. We have our own copy of the data, ensuring that modifications won’t unintentionally impact others. Reference types, primarily classes, share the same underlying object, requiring careful management.

The choice between them depends on our code’s needs. Value types offer straightforward data handling, while reference types excel in complex scenarios.

Happy coding!

--

--

Hina Khan
Women in Technology

Software Engineer— Flutter Expert | SwiftUI | Machine Learning Enthusiast