EthicsComputerCourses
Join training +91 90532 10052

Memory Management & ARC

Swift · Lesson 18 of 30 Intermediate 7 min read Updated

Every class instance your app creates uses some memory. When nobody needs the object any more, that memory must be given back, or the app slowly fills the phone's memory until iOS shuts it down. Some languages use a garbage collector that pauses now and then to find unused objects. Swift uses Automatic Reference Counting (ARC) instead: it counts how many strong references point to each object, and frees the object the moment the count reaches zero.

ARC works by itself almost all the time. But there is one trap it cannot solve alone, the reference cycle, and you must know how to avoid it. This lesson shows how ARC works, how cycles cause memory leaks, and how weak, unowned and [weak self] fix them.

How ARC counts references

ARC applies only to class instances (and closures), because only they are shared by reference. Structs and enums are copied, so they need no counting. A class can have a deinitializer, deinit, which runs just before its memory is freed; it is useful for seeing ARC at work.

Swift
class Passenger {
    let name: String

    init(name: String) {
        self.name = name
        print("\(name) boards the train")
    }

    deinit {
        print("\(name) gets off the train")
    }
}

var first: Passenger? = Passenger(name: "Aarav")
var second = first
print("Two references")

first = nil
print("One reference left")

second = nil
print("No references")
Output
Aarav boards the train
Two references
One reference left
Aarav gets off the train
No references

Creating the object gives it one strong reference (from first). Copying it to second makes two. Setting first to nil removes one, but the passenger stays, because second still holds him. Only when the last strong reference goes does deinit run, immediately. The variables are optional (Passenger?) so that we can set them to nil.

Strong reference cycles

A problem appears when two objects hold strong references to each other. Each keeps the other's count above zero, so neither is ever freed, even when your code can no longer reach them. This is a memory leak.

Swift
class Student {
    let name: String
    var mentor: Teacher?
    init(name: String) { self.name = name }
    deinit { print("Student \(name) freed") }
}

class Teacher {
    let name: String
    var student: Student?          // strong reference: creates a cycle
    init(name: String) { self.name = name }
    deinit { print("Teacher \(name) freed") }
}

var kabir: Student? = Student(name: "Kabir")
var sharma: Teacher? = Teacher(name: "Mrs Sharma")
kabir?.mentor = sharma
sharma?.student = kabir

kabir = nil
sharma = nil
print("Variables cleared")
Output
Variables cleared

No deinit message appears. After both variables are set to nil, the student still holds the teacher and the teacher still holds the student. The two objects float in memory for the rest of the program's life.

Breaking cycles with weak

A weak reference points to an object without adding to its count. When the object is freed, every weak reference to it becomes nil automatically, so a weak reference must be an optional var. Make the "back" link in a relationship weak. Here, change one line in Teacher:

Swift
class Teacher {
    let name: String
    weak var student: Student?     // weak: does not keep the student alive
    init(name: String) { self.name = name }
    deinit { print("Teacher \(name) freed") }
}

var kabir: Student? = Student(name: "Kabir")
var sharma: Teacher? = Teacher(name: "Mrs Sharma")
kabir?.mentor = sharma
sharma?.student = kabir

kabir = nil
print(sharma?.student == nil)
sharma = nil
print("Variables cleared")
Output
Student Kabir freed
true
Teacher Mrs Sharma freed
Variables cleared

With Student unchanged, setting kabir to nil removes the only strong reference to the student, so he is freed at once, and the teacher's weak student property becomes nil. When the student is freed, his strong mentor reference goes too, so clearing sharma frees the teacher.

unowned references

An unowned reference also does not keep an object alive, but it is not optional. Use it when the other object will always live at least as long, for example a credit card and its owner: a card never exists without its customer. If you access an unowned reference after its object is gone, the program crashes, so use weak when in doubt.

Swift
class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) { self.name = name }
    deinit { print("Customer \(name) freed") }
}

class CreditCard {
    let lastFour: String
    unowned let owner: Customer
    init(lastFour: String, owner: Customer) {
        self.lastFour = lastFour
        self.owner = owner
    }
    deinit { print("Card \(lastFour) freed") }
}

var meera: Customer? = Customer(name: "Meera")
if let customer = meera {
    customer.card = CreditCard(lastFour: "4821", owner: customer)
}
meera = nil
Output
Customer Meera freed
Card 4821 freed

The customer's deinit runs first; then her stored properties are released, which frees the card, since nothing else holds it.

Closures and [weak self]

Closures are reference types too, and they capture the objects they use. If an object stores a closure, and that closure uses self, you get a cycle: object holds closure, closure holds object. A capture list such as [weak self] captures self weakly instead.

Swift
class DownloadScreen {
    let title: String
    var onFinish: (() -> Void)?

    init(title: String) { self.title = title }

    func setUp() {
        onFinish = { [weak self] in
            guard let self else { return }
            print("\(self.title) finished")
        }
    }

    deinit { print("\(title) screen closed") }
}

var screen: DownloadScreen? = DownloadScreen(title: "Syllabus PDF")
screen?.setUp()
screen?.onFinish?()
screen = nil
Output
Syllabus PDF finished
Syllabus PDF screen closed

Inside the closure, self is optional; guard let self unwraps it, and if the screen is already gone the closure simply does nothing. Remove [weak self] and the last line no longer prints, because the screen and its closure keep each other alive. You only need this for escaping closures that are stored; closures passed to map or filter run immediately and cannot create cycles.

Common mistakes

  • Two strong references pointing at each other. Parent-to-child links are strong; child-to-parent links should be weak.
  • Using unowned when the object might disappear. Accessing a freed unowned reference crashes; prefer weak.
  • Forgetting [weak self] in stored closures. Timers, notification observers and long-lived callbacks are common sources of leaks.
  • Adding [weak self] everywhere. It is not needed in non-escaping closures or in structs.

Key points

  • ARC counts strong references to class instances and frees each one as soon as its count reaches zero.
  • deinit runs just before an instance is freed.
  • Two objects holding strong references to each other form a cycle that ARC cannot free.
  • weak var references do not count, and become nil when the object is freed.
  • unowned references do not count and are not optional; use them only when the object outlives the reference.
  • Capture lists like [weak self] stop stored closures from keeping their owner alive.

Exercise

Create a class Hostel with a name and an array of Room objects, and a class Room with a number and a reference back to its hostel. Choose the right kind of reference for the back link so that no cycle forms. Add deinit messages to both, create a hostel in Pune with two rooms, then set the hostel variable to nil and confirm that all three objects are freed.

Show solution
Swift
class Hostel {
    let name: String
    var rooms: [Room] = []
    init(name: String) { self.name = name }
    deinit { print("Hostel \(name) freed") }
}

class Room {
    let number: Int
    weak var hostel: Hostel?
    init(number: Int) { self.number = number }
    deinit { print("A room was freed") }
}

var hostel: Hostel? = Hostel(name: "Pune Girls Hostel")
for number in [101, 102] {
    let room = Room(number: number)
    room.hostel = hostel
    hostel?.rooms.append(room)
}
hostel = nil
Output
Hostel Pune Girls Hostel freed
A room was freed
A room was freed

The hostel owns its rooms strongly, and each room points back with a weak reference, so there is no cycle. When the hostel is freed, its rooms array is released, which frees both rooms. The room message leaves out the number because your code should not depend on the order in which an array releases its items.

Quick Quiz

1. When does ARC free a class instance?

2. Why must a weak reference be an optional var?

3. What problem does [weak self] solve in a stored closure?

4. Does ARC manage structs?

Learn Swift with a mentor

Free lessons get you started. Our Mobile App Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call