Classes and Structs in Swift: A Deeper Look

Matheus Cunha
6 min readApr 30, 2022

“What is the difference between classes and structs?”

This is probably the most asked question for iOS developers in interviews, and the most simple and popular answer is “Classes are reference types and structs are value types”. But do you deeply know what that means?

In this post we're gonna see:

  • How structs and classes work;
  • How structs and classes are stored in memory;
  • When to use structs and when to use classes.

How structs and classes work

Let’s start with the basics, what classes and structs have in common. And they have a lot in common:

  • Define properties to store values;
  • Define methods to provide functionality;
  • Define subscripts to provide access to their values using subscript syntax;
  • Define initializers to set up their initial state;
  • Be extended to expand their functionality beyond a default implementation;
  • Conform to protocols to provide standard functionality of a certain kind

However, there are some peculiarities of the classes. They have additional capabilities that Structs don’t have:

  • Inheritance enables one class to inherit the characteristics of another;
  • Type casting enables you to check and interpret the type of a class instance at runtime;
  • Classes can define deinitializers deinit() which are functions called before the instance is deallocated;
  • Reference counting allows more than one reference to a class instance. ⚠️

Now that we know the common things, let's see the differences. And the main difference is that classes are reference types, their values ​​are passed by reference to the same memory space. Structs are value types. That is, their values ​​are copied to a new memory space.

Okay, but what does that mean in practice?

Structs

Let's use The Lord of The Rings(🤓) to exemplify:

The example above defines a new struct called FellowshipMember, to describe a Fellowship Of The Ring member. This struct has one variable called name.

We declare a constant called firstMember and set it to a FellowshipMember instance initialized with "Gandalf". Next, firstMember is assigned to a new variable, called secondMember, and the name of secondMember is modified to "Frodo".

Checking the name property of secondMember shows that it has indeed changed to be "Frodo":

Now, let's check the name property of firstMember:

Still been "Gandalf". When secondMember was given the current value of firstMember, the values stored in firstMember were copied into the new secondMember instance. Structs make a copy of the original object’s structure in a new memory space (there’s the value type!). So, if you change the data of the new variable, the original object won't change.

Classes

Here's an example using the Ring class, that contains a variable bearer:

This example declares a new constant called theOneRing and sets it to refer to a new instance of the Ring class. The bearer is set to "Sauron".

Next, theOneRing is assigned to a new constant, called just ring, and the bearer of the ring is modified to "Isildur".

Now, if we print the value of theOneRing, what is expected to happen? 🤔

Yes! Because classes are reference types, theOneRing and ring actually both refer to the same Ring instance. So, if you make any changes to the new object, the original object will change. Instead of copying the entire data structure to new memory space, it just creates a reference to the current memory space.

How structs and classes are stored in memory

We have in mind the difference between classes and structs, and how both behave when objects are instantiated. Now, let’s take a deeper look and understand how value types and reference types work when we’re talking about memory. How are they stored in memory?

ARC — Automatic Reference Counting.

First, we need to know what is the ARC — Automatic Reference Counting. The ARC is responsible to manage our app's memory usage. Before the ARC, programmers had to manually collect and release memory via code. It was necessary to imagine all the possible logical scenarios that the app would go through to ensure an adequate lifetime for the object. But with ARC, we don’t need to think about memory management ourselves.

And how does it work? Every time we create a new instance of a reference type, ARC allocates a chunk of memory to store information about that instance.
When an instance is no longer needed, ARC frees up the memory used by that instance so that the memory can be used for other purposes instead.

Swift compiles the programs using two different segments of memory called Stack and Heap.

Stack

The Stack is static in memory and stores temporary data. Is very fast, quickly accessing variables from memory. Your allocation happens when the app is compiled. The stack memory segment works just like the stack data structure: the last value type allocated will be the first one to be deallocated. (Last In, First Out, or LIFO). In the stack are stored value types such as structs and enums. The variables in the stack are automatically deallocated, this memory is freed when the method exits.

  • Static in memory and stores temporary data;
  • Very fast access;
  • Compiled-time;
  • Last In, First Out;
  • Store value types such as struct and enums.

Heap

Unlike the Stack, the Heap is slow, but it’s more dynamic, capable of storing more complex objects that have a lifetime, such as reference types: classes and closures. There is no order here, the objects can be accessed and removed randomly and the allocation happens when the app is running. When a certain amount of memory is quested to allocate an object, it’s necessary to search the heap data structure to find an empty block of memory of the appropriate size. When the memory is not being used anymore, the heap frees that section of memory (Thanks ARC!).

  • Dynamic in memory and stores objects that have a lifetime;
  • Slow access;
  • Run-time;
  • Randomly;
  • Store reference types such as classes and closures;

The main difference between Stack and Heap is in how they pass data:

Stack passes by value. So, when we access a struct, we get a copy of the value. Whatever the changes we make to the things from a Stack are local, one doesn’t affect the other. Besides, we don’t need to worry about the memory management of the stack memory segment. Because they are passed by value, the compiler knows and understands when to dispose of instances of structs and enums.

Heap passes by reference, and in this case, you get a pointer of the data. If we have a class and we make a change, this is gonna affect all the other instances of that class. As we saw earlier, ARC is responsible for managing the memory of reference types. It doesn’t mean that we shouldn’t need to worry about memory management. There are times that ARC isn’t able to figure out when it is safe to deallocate a reference type instance. To avoid some problems such as retain cycles and memory leaks, we need to take some care (read more about it here).

Value types are stored in the Stack and reference types are stored in the Heap.

When to use Structs and when to use Classes

Well, we don’t have a simple answer for when to use structs or classes. But Apple itself recommends using structs by default unless you intentionally need an object that needs to have its data shared and changed at different points in your app. It’s recommended to use a class if you need the specific features of a class. This is why working with structs is the default.

Knowing the difference between structs and classes and when to use them greatly impact how you code your apps!

Thanks for reading! If you liked this article, please clap so other people can read it too 🙂

--

--