Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 1

Understanding ARC: Memory Management in Swift

Recommended resources

Hello! Welcome to your first lesson in our course on professional iOS development.

Your goal is to fill in foundational gaps to advance your skills, and you’ve pointed out that memory management is a key area you want to strengthen. We'll start right there. This module focuses on Memory Management and Performance, and today's lesson lays the groundwork for everything that follows.

Our learning outcome is to explain how Automatic Reference Counting (ARC) manages memory and object lifecycles. We'll explore what ARC is, how it works under the hood, and why it's so important for building stable, high-performance iOS applications.

1. The Foundation: Value vs. Reference Types

Before diving into ARC, we need to quickly revisit a core concept in Swift: the difference between value types (struct, enum) and reference types (class). This distinction is the entire reason ARC exists.

Reference types are stored in a region of memory called the heap, and your code holds a reference (or pointer) to the object's location on the heap. Value types are typically stored on the stack and are copied when passed around.

ARC's job is to manage the lifecycle of objects on the heap.

To get a clear visual and code-based explanation of these concepts, please watch the following segments from a video by Swiftful Thinking.

Swift: Struct vs Class vs Actor, Value vs Reference Types, Stack vs Heap | Swift Concurrency #8

This video, 'Swift: Struct vs Class vs Actor', provides an excellent breakdown of the fundamental differences in how data is stored and managed in Swift. We'll focus on the sections that contrast value and reference types, and the stack vs. the heap.

Please watch the following two parts: Classes as Reference Types (from 11:14 to 19:36): This part demonstrates how multiple variables can refer to the same class instance in memory. Stack vs. Heap (from 43:24 to 45:34): This segment visualizes where value types (like structs) and reference types (like classes) are stored.

The key takeaway is that multiple parts of your app can hold a reference to the same class instance. This raises a critical question: when is it safe to remove that object from memory? That's the problem ARC solves.


2. What is Automatic Reference Counting (ARC)?

Now that we've established why we need memory management for class instances, let's define what ARC is.

Unlike garbage collection (GC) seen in languages like Java or C#, which periodically scans for and cleans up unused objects at runtime, ARC is a compile-time feature. The Swift compiler analyzes your code and automatically inserts memory management calls (retain and release) for you.

To get a concise overview of this process, let's watch a short clip.

ARC, References & Retain Cycles | Intermediate Memory Management | Swift & iOS Basics

This video from The Code Bird, 'ARC, References & Retain Cycles', gives a quick and clear introduction to the principle of reference counting.

Watch the segment from 01:03 to 04:46. Focus on: The core principle: an object stays in memory as long as it has at least one active reference. The fact that ARC automates this by inserting retain and release calls at compile time, which means there is very little runtime performance overhead.

This compile-time approach is a major reason for Swift's performance. It's deterministic and avoids the "stop-the-world" pauses that can occur with some garbage collectors, which is critical for a smooth user experience on mobile devices.

ARC vs. Garbage Collection Comparison
This diagram illustrates the difference between ARC and Garbage Collection (GC). ARC (left) deallocates memory for an object as soon as it's no longer referenced, leading to more predictable and lower CPU usage. GC (right) cleans up memory in periodic cycles, which can cause spikes in CPU activity.

3. How ARC Manages Object Lifecycles

At its core, ARC is simple:

  • For every class instance, ARC maintains a reference count.
  • This count tracks how many properties, constants, and variables currently have a strong reference to that instance.
  • The instance is kept in memory as long as its reference count is greater than zero.
  • If the reference count drops to zero, ARC immediately deallocates the instance and frees its memory.

Let's see this in action with the official Swift documentation, which is the ultimate source of truth.

Automatic Reference Counting - Documentation | Swift.org

The official Swift documentation provides the most precise explanation of ARC. We'll read the introductory sections that demonstrate the lifecycle of an object with code.

Please read the sections titled 'How ARC Works' and 'ARC in Action'. Focus on the Person class example. Pay close attention to when the init and deinit messages are printed as references are created and then set to nil.

Let's break down the Person example from the documentation:

  1. reference1 = Person(name: "John Appleseed")

    • A new Person instance is created. Its init method runs.
    • ARC allocates memory for this instance.
    • reference1 holds a strong reference to the instance.
    • Reference Count: 1
  2. reference2 = reference1
    reference3 = reference1

    • Two more strong references are created, pointing to the same instance.
    • Reference Count: 3
  3. reference1 = nil
    reference2 = nil

    • Two strong references are broken.
    • Reference Count: 1
    • The Person instance is not deallocated because reference3 still holds a strong reference.
  4. reference3 = nil

    • The final strong reference is broken.
    • Reference Count: 0
    • ARC now sees that nothing is using the instance. It deallocates it, freeing the memory. The deinit method is called just before deallocation.
Test your understanding!

Consider the following code:

class Engine {
    init() { print("Engine initialized") }
    deinit { print("Engine deinitialized") }
}

var primaryEngine: Engine? = Engine()
var backupEngine: Engine? = primaryEngine
var testEngine: Engine? = Engine()

primaryEngine = nil

After this code executes, how many Engine instances are still in memory?

Show answer

There are still two Engine instances in memory.

  1. The first Engine instance (let's call it E1) is created and assigned to primaryEngine (Count: 1).
  2. backupEngine is assigned primaryEngine, so backupEngine also points to E1 (Count for E1: 2).
  3. A new, second Engine instance (E2) is created and assigned to testEngine (Count for E2: 1).
  4. primaryEngine is set to nil. The reference count for E1 goes down by one (Count for E1: 1).
  5. E1 is not deallocated because backupEngine still holds a strong reference to it. E2 is not affected at all.

4. Shifting Your Mindset

As an experienced developer, you know that memory leaks are a serious problem. While ARC automates most of the work, it's not magic. The most common source of memory leaks in Swift is the strong reference cycle, where two or more objects hold strong references to each other, preventing their reference counts from ever reaching zero.

The key to mastering memory management is to shift your thinking from "managing memory" to "managing object relationships."

ARC in iOS: The Memory Management Revolution That ...

This article from ArshTechPro provides a great perspective for developers already familiar with iOS, contrasting ARC with the old days of Manual Reference Counting (MRC) and framing how a professional should think about ARC today.

Please read the sections titled 'The Modern ARC Mindset' and 'Common Misconceptions about ARC'. This will help solidify why a deep understanding of ARC is still crucial.

As the article notes:

  • A strong reference (the default) says: "I own this object. It cannot disappear while I exist."
  • A weak reference says: "I would like to refer to this object, but I don't own it. It can disappear at any time."
  • An unowned reference says: "I need to refer to this object, and I guarantee it will outlive me."

Understanding these relationship types is the key to preventing leaks.


Conclusion

In this lesson, we've established the fundamental mechanics of memory management in Swift.

Key Takeaways:

  • ARC is a compile-time process that manages memory for reference types (classes) stored on the heap.
  • It works by tracking the number of strong references to an object.
  • An object's lifecycle begins with init and ends with deinit when its reference count drops to zero.
  • Mastering ARC is about understanding and defining the ownership relationships between your objects, not just counting references.

You now have a solid foundation for understanding how Swift manages memory. The "just works" part of ARC handles most cases, but the real professional challenge lies in handling the cases it can't solve on its own.

Preview of the Next Lesson:
In our next session, we will tackle the most common problem you'll face with ARC: strong reference cycles. We will learn how to use weak and unowned references to break these cycles in common scenarios like delegates and closures, which is the first step toward writing truly leak-free code.