Skip to content
Theory · Memory and ARC · lesson 3 of 7

A strong reference cycle

Back to the library. Anna borrowed a book from Boris, and Boris borrowed one from Anna, and they agreed: each will return the other's book only after getting their own back. Anna waits for Boris, Boris waits for Anna, and neither book ever goes back to the shelf.

Objects can get into the same situation. An object is freed when no strong references to it are left. But what happens if two objects hold strong references to each other? Take a person and an apartment: the person knows where they live, and the apartment knows its tenant.

SWIFT
class Person {
    var home: Apartment?
    deinit { print("Person freed") }
}
class Apartment {
    var tenant: Person?
    deinit { print("Apartment freed") }
}

var anna: Person? = Person()
var flat: Apartment? = Apartment()
anna?.home = flat
flat?.tenant = anna
anna = nil
flat = nil

Let's go through the program:

  1. Person has a property home — a reference to an apartment, and Apartment has a property tenant — a reference to the person living there. Both properties are optional: a person may live without an apartment, and an apartment may stand without a tenant. An optional var property with no value starts as nil: that is its default value, so Person() and Apartment() are created with the empty init().
  2. The lines with anna and flat create one object each, and so far each object has one strong reference.
  3. anna?.home = flat gives the apartment a second reference, and flat?.tenant = anna gives the person a second one.
  4. anna = nil and flat = nil remove the outside references. The program prints nothing: neither deinit ran.

Let's count the references after each of the last lines:

LineReferences to PersonReferences to Apartment
flat?.tenant = anna22
anna = nil12
flat = nil11

After the last line, each object still has one reference — from the other object. Zero will never come.

Each waits for the other

Objects that hold each other with strong references in a loop form a strong reference cycle. The loop can be longer too: object A holds B, B holds C, and C holds A.

Why the cycle does not break by itself:

  1. The person is freed only when the apartment's tenant reference goes away.
  2. That reference goes away only when the apartment itself is freed.
  3. The apartment is freed only when the person's home reference goes away.
  4. And the home reference goes away only when the person is freed.

Each object waits for the other, like Anna and Boris.

A memory leak

The objects of the cycle stay in memory to the end of the program, even though nothing can reach them any more: anna and flat now hold nil. Memory that a program took and can neither use nor give back is called a memory leak.

In a small program you cannot see it. But an app runs for hours. If every time a screen opens it leaves a couple of such objects in memory, memory slowly runs out, and the system closes the app.

Here the picture with the readers is off. People will notice in the end that they are waiting for each other, and they will sort it out. Swift does not look for cycles and does not break them: the objects wait until the program ends, and the compiler gives no error or warning about it.

Interviewers like to ask why memory can leak under ARC. You can build the answer like this:

  • ARC frees an object only when the count of references to it reaches zero;
  • in a strong reference cycle the counts never drop below one, even when nothing outside can reach the objects;
  • a garbage collector searches for objects that no variable can reach and would free such a cycle, while ARC searches for nothing.

Breaking a cycle by hand

You can break a cycle yourself: remove one of the references between the objects while you can still reach them.

SWIFT
var boris: Person? = Person()
var room: Apartment? = Apartment()
boris?.home = room
room?.tenant = boris
room?.tenant = nil
boris = nil // Person freed
room = nil // Apartment freed

Let's see what changed:

  1. room?.tenant = nil removes the apartment's reference to the person. Person is left with one reference — boris.
  2. boris = nil frees the person. Its home property goes away with it, and the apartment is left with one reference — room.
  3. room = nil frees the apartment.

This works, but it is fragile: you have to remember the line that breaks the cycle every time, and if you forget it, the leak comes back silently.

Common mistake

The cycle is broken too late — after the outside reference has been let go:

SWIFT
var carl: Person? = Person()
var studio: Apartment? = Apartment()
carl?.home = studio
studio?.tenant = carl
carl = nil
carl?.home = nil // ✗
studio = nil

The program prints nothing. By the line carl?.home = nil, the variable carl is already nil, and ?. skips the assignment. The Person object can no longer be reached, so there is nothing left to break the cycle with.

Move carl?.home = nil above the line carl = nil. Then both deinits run on the line studio = nil.

Key points

  • A strong reference cycle is objects holding each other in a loop; their counts never reach zero.
  • nil in the outside variables does not break the cycle: neither deinit runs, and the memory leaks.
  • ARC does not look for cycles or break them, while a garbage collector would free such a cycle.
  • To break a cycle by hand, remove a reference between the objects while you can still reach them.