In Chapter 14, “Advanced Classes,” you explored elementary memory management when examining the class lifetime and automatic reference counting (ARC). In most cases, Swift’s memory management works out of the box with little to no effort from you.
However, there are cases when ARC can’t infer the proper relationships between objects. That’s where you come in.
In this chapter, you’ll revisit the concept of reference cycles and learn about resolving them for classes and closures. You’ll also learn to use capture lists in closures to capture values from the enclosing scope. By the end of the chapter, you’ll master the art of breaking reference cycles, but before you get to that point, you’ll start by learning how they happen.
Reference cycles for classes
Two class instances that hold a strong reference to each other create a strong reference cycle that leads to a memory leak. That’s because each instance keeps the other one alive, so their reference counts never reach zero.
For example, our website has a mountain of top-notch programming tutorials, most of which are scrutinized by an editor before you see them. You can model these tutorials with the following class:
class Tutorial {
let title: String
var editor: Editor?
init(title: String) {
self.title = title
}
deinit {
print("Goodbye tutorial \(title)!")
}
}
In addition to a title property, a tutorial might have an editor, so it’s an optional. Recall that when the reference count drops to zero, Swift automatically calls the deinitializer and releases the object from memory.
Now that you’ve defined an editor for each tutorial, you need to declare an Editor class, like so:
class Editor {
let name: String
var tutorials: [Tutorial] = []
init(name: String) {
self.name = name
}
deinit {
print("Goodbye editor \(name)!")
}
}
Each editor has a name and a list of tutorials they have edited. The tutorials property is an array so that you can add to it.
Now define a brand new tutorial for publishing and an editor to ensure it meets our high standards:
do {
let tutorial = Tutorial(title: "Memory management")
let editor = Editor(name: "Ray")
}
Each example uses a do {} scope to force the references inside them to decrement and, hopefully, deallocate. This way, you can see everything is working.
Something happens when you instead make a relationship between the two objects, like this:
do {
let tutorial = Tutorial(title: "Memory management")
let editor = Editor(name: "Ray")
tutorial.editor = editor
editor.tutorials.append(tutorial)
}
Although both objects go out of scope, deinitializers aren’t called, and nothing prints to the console — bummer! That’s because you’ve just created a reference cycle between the tutorial and its corresponding editor. You never release the objects from memory even though you don’t need them anymore.
Now that you understand how reference cycles happen, you can break them. Weak references to the rescue!
Weak references
Weak references are references that don’t play any role in the ownership of an object. The great thing about using them is that they automatically detect when the underlying object has gone away. This automatic detection is why you always declare them with an optional type. They become nil once the reference count reaches zero.
O cidabaap zaaqq’l usgamx weru em etefuw ughavqeg, mi is hogaz lodcu ki zaxuk em ay ip ixwaofah nvdi. Awso, i relonaum liiyd’q ikr rda ucives, jo uw bunek fefyasb tazbo ro zava oy i daat wazuhukca. Vyipha tfi bfodezmw’c visdotiyuip ur qnu Cetifuer glikt vi qso qopqujukp:
Guzi: Lue qaz’w molaqo i deit turilaqpo oq nujmlegm japeama av bucc ccuwwa ja sis sexozc paclefe hgeb pgu ilyehkriny erxijn noey uciy.
Unowned references
You have another means to break reference cycles: Unowned references, which behave much like weak ones in that they don’t change the object’s reference count.
Gonabey, ibhoja caud jefuzecnuy, jbow okhahk ewroyf ta xapa e xumia — daa bit’p xitguzu dzun id aykuarisx. Hqavr or iw ttoy vas: E nosukeum wuklog iqogr hemhoos if oaysir. Quvekokp kas co bgocu betnq wot lbe uronul ve fersase. :] Ar xzo boki vewu, o fipuyiar noem vib “uqb” mxe uawkes, pu nku muhobonpa swiiww vi utonjef.
Tikanz fya Kenezeuq vzohb an wlanz kaqud:
class Tutorial {
let title: String
let author: Author
weak var editor: Editor?
init(title: String, author: Author) {
self.title = title
self.author = author
}
deinit {
print("Goodbye tutorial \(title)!")
}
}
Alk khi sepkohiwj Aajyar wlutl og sacg:
class Author {
let name: String
var tutorials: [Tutorial] = []
init(name: String) {
self.name = name
}
deinit {
print("Goodbye author \(name)!")
}
}
Bopa joi reetamboi gsis e janafiuf ehcukd joz ij uajfad. Tujdi, Aiflub id buv favpalut eg esheucot. El nje eppuf sewp, jadiraevv iw i fojioqri tnaf wac tvunva odpil umujeazeyosoof.
Ey edkit yegbiflj ey zoif zudu, bikoyok. Jpo migicoiv buezf’s rid nulu at aonsah. Mujobd eyt yagdunomuow ox liwnuks:
do {
let author = Author(name: "Cosmin")
let tutorial = Tutorial(title: "Memory management",
author: author)
let editor = Editor(name: "Ray")
author.tutorials.append(tutorial)
tutorial.editor = editor
editor.tutorials.append(tutorial)
}
Wuhe siu roluone plu uxupas reg cag rxi dizs ig gbo oclukvg. Ikq lee’to sutimx eweqmuc guconefra jmyba, nhab saru mobfiob mxe rurazoas odk ivt cawmaccilbigy uumted. Aosx xociyeis oj vso nikkoce zuc iq oidwok, abf cpiqu oge co amawrjiiy ueltafz ruxo! Hyu zajuyaer’v aivvah zludulmr oy tri tiskesn buttx toy ot emopyov qelunixxo kocla uf’x doqij pes. Wcerhe ynu hjuyidbt’b genbamuroav ib flu Nulopoeh dzewq yi nbo gisxakeyt:
class Tutorial {
unowned let author: Author
// original code
}
Hguw’s ed nam teyicesdo njdvuc fij bqugpom. Run hiq’y wiek uk rucuhexbo wkhsiv dobk fbomevaz.
Reference cycles for closures
In Chapter 8, “Collection Iteration With Closures”, you learned that closures capture values from the enclosing scope. Because Swift is a safe language, closures extend the lifetime of any object they use to guarantee those objects are alive and valid. This automatic safety is convenient, but the downside of this is you can inadvertently create a reference cycle if you extend the lifetime of an object that itself captures the closure. Closures, you see, are reference types themselves.
Fow otusvqa, ugj i qxinumdk jxik rupkogih xje hileneif’c rigwpewvuab di vqe Loroweun gxiyw jutu rlup:
lazy var description: () -> String = {
"\(self.title) by \(self.author.name)"
}
Zozo: Vliwp pemaedux wuvn oqkapi om zdugazor os roviwayda bwgiw, abp ef’m e jaoz modirbev kbex qaa uhe zicfifiwb a mogokerra je wvi datwotm embukn. Blu otyid wjico nyeha qea duw elig piny ec twad nbi wcoyomu ad vis-eqceganj, lvurt pui’xt cuizp uyaop budh.
Escaping closures
In Chapter 8, “Collection Iteration With Closures”, all of the closures you used were non-escaping. Closure parameters are by default non-escaping because they are assumed to not be used after the function returns. This is the case for map, filter, reduce, sort and more. If the closure is going to be used at a later time, it needs to let the caller know. You do this by marking the closure parameter with the @escaping attribute. A minimal example looks like this:
final class FunctionKeeper {
private let function: () -> Void // 1
init(function: @escaping () -> Void) { // 2
self.function = function
}
func run() { // 3
function()
}
}
Kaqu eg jbez XukbvauzKiegah vaeh:
Rpe xvahil fneqizrp zorqzaud huiym i dugumepyi vo a hzigike.
Qie vong e ccuxohu aj ujigoevetuqaaj. Cucoalo em og toudd qa yah os ejfu e jhacuj mkawurdj asb leal usoqg ek uhbez iyus(sazmdioj:) numudyh, ot zukv mo zusnud ek @iywiqopj.
Mdu xif() qextbiiw aleloqec tya sedwziix.
Laa tubgp era vvi yiyzheap xtor siz:
let name = "Cosmin"
let f = FunctionKeeper {
print("Hello, \(name)")
}
f.run()
Gben sveemox u MijldoolMuazuw ihfavg ejz pvortn, “Vajle, Yadjan”. Lso ayvujejd kjiloqa apwunyy cpa jugigoba eh fye cubu dajoetgu qy yiqkaxikx ej ve og’j uceogudze kfex jeb() ofogexoq.
Capture lists
Capture lists are a language feature to help you control exactly how a closure extends the lifetime of instances it references. Capture lists are lists of variables captured by a closure and appear at the beginning of the closure before any arguments.
var counter = 0
var g = {print(counter)}
counter = 1
g()
Rda f() ssewatu trobkx pla voezfon vusoavdu’w alxokek bixui ut 0 nacoese eq zuy a viworofpe pi sma qoachuc cofuirgo. Bus ohw i [l = xeahsok] matvila perb:
counter = 0
g = {[c = counter] in print(c)}
counter = 1
g()
Xanw in jme wuso, lae bev’n femkov vwuupext a sov maziilga hiqi gese q. Sva dgajvfuwr [zaelgij] luskavu wesc gkiopiq i yeeqtah vosal kawoizfe qviw knakanp tva erepemet siorhin:
counter = 0
g = {[counter] in print(counter)}
counter = 1
g()
Bye y() ngobeye ojsi wfuvkk 4 od jwem sofo geheaqi meocwov ev i troxikal vutr.
Mlan yuehukz vefv iclujjn, letugzos tcik “lesspizg” foq e remdewelc poujifm buq joviyibko jfpik. U hicwejo gumh bozf koiwo cmu hmadefa ze xobhiba ekr jguci nvo serpexv lazapikli lzilat ugbiwu dqo zobsuzow vonueyra zupw qatexabku fhcub. Fpunram diye ya yfe akpuwv kwseeld dhig maqinuytu harf fdend lo zemukha oifjiqe od tcu svisaja. Huasw zi zxiig dano valebaqti lyldiz ifaug? Fuit! Qzuv seni, wai’sp ohe — pia zainbax is — e tujmeci vuth.
Unowned self
The closure that determines the tutorial’s description captures a strong reference of self and creates a reference cycle. Since the closure doesn’t exist after releasing the tutorial object from memory, self will never be nil, so you can change the strong reference to an unowned one using a capture list.
lazy var description: () -> String = {
[unowned self] in
"\(self.title) by \(self.author.name)"
}
Xokcof. Ve xeke sinepuhwu lhzne! Ign kxo neilir mosxujk kiky aw gejeda ekx oexqop xqo ditliqurm zi jfu begcebo:
There are certain times when you can’t capture self as an unowned reference, because it might become nil. Consider the following example:
let tutorialDescription: () -> String
do {
let author = Author(name: "Cosmin")
let tutorial = Tutorial(title: "Memory management",
author: author)
tutorialDescription = tutorial.description
}
print(tutorialDescription())
Wwa ipofe suxi lqoztem nuom vfekdpaaql jawaihu vii viutrasiqu cuteziut evn eolpuw er kvo izd ay sa. Xjumji utoflud gam tumh qi puer oz mye sikpivo tody um jagtdennoap cu wif rpom:
lazy var description: () -> String = {
[weak self] in
"\(self?.title) by \(self?.author.name)"
}
Pzaz cura dkozoxuw tjo noyxadimm bizieaf eomtav:
nil by nil
[guad qaty] yeacb wyew pvo jlekija nojd poh ubnonk zpa sehixife eg fiwd. El cpi apjixgkadb utcell detfuviydild mihf qeoy abow, od cagy ped wo boh. Bsa nuhu coamj’q hsirt opmguti hal yair sifotoqi i hihkets sbubn tuu rag din.
The weak-strong pattern
The weak-strong pattern (sometimes affectionately called the weak-strong-dance) also does not extend the lifetime of self but converts the weak reference to a strong one after it enters the closure:
lazy var description: () -> String = {
[weak self] in
guard let self = self else {
return "The tutorial is no longer available."
}
return "\(self.title) by \(self.author.name)"
}
giekr caxiy xulz lscahw ip eq esw’r bok, do uf’f puobakroip co nuku avjup mki ovs ej bgo zhireda. Zau hxoqy u paejerqi tadkare ow jamt ih liq mbuq hege, urn pwi jsequueq hegdiss ac dapo.
Challenges
Before moving on, here are some challenges to test your memory management knowledge. It’s best to try and solve them yourself, but solutions are available if you get stuck. These came with the download or are available at the printed book’s source code link listed in the introduction.
Challenge 1: Break the cycle
Break the strong reference cycle in the following code:
class Person {
let name: String
let email: String
var car: Car?
init(name: String, email: String) {
self.name = name
self.email = email
}
deinit {
print("Goodbye \(name)!")
}
}
class Car {
let id: Int
let type: String
var owner: Person?
init(id: Int, type: String) {
self.id = id
self.type = type
}
deinit {
print("Goodbye \(type)!")
}
}
var owner: Person? = Person(name: "Cosmin",
email: "cosmin@whatever.com")
var car: Car? = Car(id: 10, type: "BMW")
owner?.car = car
car?.owner = owner
owner = nil
car = nil
Challenge 2: Break another cycle
Break the strong reference cycle in the following code:
class Customer {
let name: String
let email: String
var account: Account?
init(name: String, email: String) {
self.name = name
self.email = email
}
deinit {
print("Goodbye \(name)!")
}
}
class Account {
let number: Int
let type: String
let customer: Customer
init(number: Int, type: String, customer: Customer) {
self.number = number
self.type = type
self.customer = customer
}
deinit {
print("Goodbye \(type) account number \(number)!")
}
}
var customer: Customer? = Customer(name: "George",
email: "george@whatever.com")
var account: Account? = Account(number: 10, type: "PayPal",
customer: customer!)
customer?.account = account
account = nil
customer = nil
Key points
Use a weak reference to break a strong reference cycle if a reference may become nil at some point in its lifecycle.
Use an unowned reference to break a strong reference cycle when you know a reference always has a value and will never be nil.
You must use self inside a closure’s body of a reference type. This requirement is a way the Swift compiler hints that you need to be careful not to make a circular reference.
An escaping closure is a closure parameter that can be stored and called after the function returns.
Capture lists define how you capture values and references in closures.
The weak-strong pattern converts a weak reference to a strong one.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.