Memory Management & ARC
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.
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")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.
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")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:
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")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.
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 = nilCustomer 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.
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 = nilSyllabus 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
unownedwhen the object might disappear. Accessing a freed unowned reference crashes; preferweak. - 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.
deinitruns just before an instance is freed.- Two objects holding strong references to each other form a cycle that ARC cannot free.
weak varreferences do not count, and becomenilwhen the object is freed.unownedreferences 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
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 = nilHostel 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?
ARC frees an object immediately when its strong reference count drops to zero.
2. Why must a weak reference be an optional var?
A weak reference can change to nil at any time, so it must be an optional variable.
3. What problem does [weak self] solve in a stored closure?
Capturing self weakly stops the closure from keeping its owner alive.
4. Does ARC manage structs?
Reference counting applies to class instances and closures; structs have no deinit.
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
