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.
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 = nilLet's go through the program:
Personhas a propertyhome— a reference to an apartment, andApartmenthas a propertytenant— 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 optionalvarproperty with no value starts asnil: that is its default value, soPerson()andApartment()are created with the emptyinit().- The lines with
annaandflatcreate one object each, and so far each object has one strong reference. anna?.home = flatgives the apartment a second reference, andflat?.tenant = annagives the person a second one.anna = nilandflat = nilremove the outside references. The program prints nothing: neitherdeinitran.
Let's count the references after each of the last lines:
| Line | References to Person | References to Apartment |
|---|---|---|
flat?.tenant = anna | 2 | 2 |
anna = nil | 1 | 2 |
flat = nil | 1 | 1 |
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:
- The person is freed only when the apartment's
tenantreference goes away. - That reference goes away only when the apartment itself is freed.
- The apartment is freed only when the person's
homereference goes away. - And the
homereference 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.
var boris: Person? = Person()
var room: Apartment? = Apartment()
boris?.home = room
room?.tenant = boris
room?.tenant = nil
boris = nil // Person freed
room = nil // Apartment freedLet's see what changed:
room?.tenant = nilremoves the apartment's reference to the person.Personis left with one reference —boris.boris = nilfrees the person. Itshomeproperty goes away with it, and the apartment is left with one reference —room.room = nilfrees 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:
var carl: Person? = Person()
var studio: Apartment? = Apartment()
carl?.home = studio
studio?.tenant = carl
carl = nil
carl?.home = nil // ✗
studio = nilThe 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.
nilin the outside variables does not break the cycle: neitherdeinitruns, 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.