Swift Reference Lifecycle
The reference lifecycle is the span from the moment a class instance is created to the moment its last strong reference goes away. Watching that span in nested ownership shows exactly which deinit runs first and which runs last.
An owner releases before its stored property
The Car class holds an Engine in a stored property. Setting the optional holder to nil runs the Car deinit first, and the Engine deinit follows when the stored property is released.
class Engine {
deinit {
print("Engine released")
}
}
class Car {
let engine = Engine()
let name = "Roadster"
deinit {
print("Car released")
}
}
func build() {
var car: Car? = Car()
if let built = car {
print(built.name)
}
car = nil
print("Garage empty")
}
build()
print("Day continues")Three levels of nesting unwind outside in
An Outer holds a Middle, and a Middle holds a Core. Clearing the one optional holder unwinds the whole chain, and the deinit lines print from the outermost owner down to the innermost one.
class Core {
deinit {
print("Core released")
}
}
class Middle {
let core = Core()
deinit {
print("Middle released")
}
}
class Outer {
let middle = Middle()
func label() -> String {
return "Outer built"
}
deinit {
print("Outer released")
}
}
func build() {
var outer: Outer? = Outer()
if let shell = outer {
print(shell.label())
}
outer = nil
print("Scope cleared")
}
build()
print("Program continues")A weak back reference breaks the cycle
The Parent holds its Child strongly, while the Child points back with a weak reference. Clearing the parent holder releases the parent first, and the child follows because nothing strong holds it any more.
class Parent {
var child: Child?
deinit {
print("Parent released")
}
}
class Child {
weak var parent: Parent?
deinit {
print("Child released")
}
}
func link() {
var parent: Parent? = Parent()
parent?.child = Child()
parent?.child?.parent = parent
print("Linked")
parent = nil
print("Link cleared")
}
link()
print("Program continues")Each optional clears at its own line
Two Token instances sit in two separate optional holders. Clearing the first holder prints its deinit between the surrounding lines, and clearing the second holder does the same later.
class Token {
let name: String
init(name: String) {
self.name = name
}
deinit {
print("\(name) released")
}
}
var first: Token? = Token(name: "First")
var second: Token? = Token(name: "Second")
print("Both alive")
first = nil
print("Middle")
second = nil
print("None alive")Example
class Machine {
deinit {
print("Machine released")
}
}
class Workshop {
let machine = Machine()
func status() -> String {
return "Workshop open"
}
deinit {
print("Workshop released")
}
}
func run() {
var shop: Workshop? = Workshop()
if let open = shop {
print(open.status())
}
shop = nil
print("Workshop closed")
}
run()Key points
- A deinit block runs exactly once, at the moment the last strong reference to the instance goes away.
- The deinit body of an owner runs before the stored class properties of that owner are released.
- Nested ownership releases from the outside in, so the outermost owner prints its deinit line first.
- A weak reference does not add to the reference count of the instance it points at.
- A strong cycle breaks when the back reference is weak, and both instances can then release.
- Setting an optional holder to nil releases its instance at that line when it was the last strong reference.
In this example, the Workshop holds its Machine in a stored property, and the run function holds the Workshop in an optional. The status line prints while both instances are alive. Setting the holder to nil runs the Workshop deinit first, then releases the stored Machine, and only then does the closing line print.
Note
A value held only by a top level let may not run its deinit before the program ends, so release points in this lesson sit inside functions or optional holders.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
An owner class holds another class instance in a stored property, and the last strong reference to the owner goes away. Which deinit runs first?