Closures and [weak self]
In the closures theme there was a recipe card with a note clipped to it: a closure carries along everything it captured. Now picture a library book with a borrower's card for that very book in its pocket. While the card is in the pocket, the book counts as checked out. And to take the card out, the book first has to go back on the shelf.
This happens when an object stores a closure, and the closure uses that same object through self:
class Player {
let name = "Anna"
var onFinish: () -> Void = {}
init() {
onFinish = { print("\(self.name) finished") } // ✗
}
deinit { print("Player freed") }
}
var player: Player? = Player()
player?.onFinish() // Anna finished
player = nilLet's go through the program:
onFinishis a property that stores a closure with no parameters and no result, hence the type() -> Void. At first it holds the empty closure{}.- In
init, a new closure goes intoonFinish. It usesself.nameand so capturesself, that is, the object itself, with a strong reference. player?.onFinish()calls the closure, and the program printsAnna finished.player = nilremoves the outside reference, but there is noPlayer freedline:deinitdid not run.
This is a strong reference cycle. The object holds the closure through the onFinish property, and the closure holds the object through the captured self. The second member of the cycle here is not an object but a closure.
Here the pocket picture is off: you see a card in a pocket right away, while the capture hides in the text of the closure. To make it visible, Swift requires you to write self. explicitly. Without it, the line print("\(name) finished") does not compile:
error: reference to property 'name' in closure requires explicit use of 'self' to make capture semantics explicitThe capture list
A cycle is broken the same way as between two objects: one of the references becomes weak. The onFinish property has to hold its closure, so the closure's reference to the object is the one made weak. That is what the capture list is for — square brackets before the word in that say how values are captured:
class Player {
let name = "Anna"
var onFinish: () -> Void = {}
init() {
onFinish = { [weak self] in
guard let self else { return }
print("\(self.name) finished")
}
}
deinit { print("Player freed") }
}
var player: Player? = Player()
player?.onFinish() // Anna finished
player = nil // Player freedLet's see what changed:
[weak self]captures the object as aweakreference: the closure knows the object but does not hold it.- Inside the closure,
selfis now an optionalPlayer?: by the time of the call, the object may be gone. guard let self else { return }is the short form ofguard let self = self, the same asif let score. If the object is alive, below itselfis a plainPlayer. If not, the closure leaves right away.player = nilremoves the only strong reference, and the program printsPlayer freed.
When the object is already gone
A closure can outlive its object, for example when it was saved in another constant:
var guest: Player? = Player()
let finish = guest!.onFinish
guest = nil // Player freed
finish()The constant finish holds the closure itself, but not the object. So guest = nil frees the object, and by the call finish(), self inside the closure is already nil. guard leaves, and the program prints nothing.
Other forms
Through self?.
Instead of guard let self, you can reach the object through ?., as with any optional: print(self?.name ?? "nobody"). If the object is already gone, such a closure prints nobody.
[unowned self]
[unowned self] does not hold the object either, and self inside the closure is not an optional. But if you call such a closure after the object is freed, the program crashes, as with any unowned reference. That is why [weak self] is written more often.
Interviewers like to ask this: why do you need [weak self]? You can build the answer like this:
- an object that stores a closure holds it with a strong reference;
- a closure that uses
selfholds the object too, and that makes a strong reference cycle; [weak self]captures the object as aweakreference, so there is no cycle;- inside the closure,
selfbecomes an optional, and you check it withguard let self.
Common mistake
You wrote [weak self] but access the property as before:
class Coach {
let name = "Boris"
var onStart: () -> Void = {}
init() {
onStart = { [weak self] in
print(self.name) // ✗
}
}
}error: value of optional type 'Coach?' must be unwrapped to refer to member 'name' of wrapped base type 'Coach'The program does not compile: after [weak self], self inside the closure is an optional Coach?, and its properties cannot be read directly. Add guard let self else { return } at the start of the closure, or write print(self?.name ?? "nobody").
Key points
- An object that stores a closure and a closure that uses
selfhold each other: that is a strong reference cycle. - Swift requires
self.to be written explicitly in such a closure, so the capture is visible. [weak self]in the capture list captures the object as aweakreference; inside,selfis an optional, checked withguard let self.- If the object is already gone,
selfin the closure isniland the call is safe, while with[unowned self]the program crashes.