You explored elementary memory management in Chapter 14, “Advanced Classes”, when you explored the class lifetime and automatic reference counting (ARC). In most cases, memory management in Swift 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 how 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 are formed.
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 it. 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 propery, a tutorial might have an editor so it’s marked as an optional. Remember from Chapter 14, “Advanced Classes”, that Swift calls the deinitializer automatically right before it releases the object from memory and its reference count becomes zero.
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 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")
}
These are placed in a scope (created with do {}) so that as soon as they go out of scope the references to them are dropped and they are correctly deallocated. Everything is working fine.
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 is why they are always declared with an optional type. They become nil once the reference count reaches zero.
O tobaliuw tuand’b adrisv muye ud iqiyab istergot, lu ek hovah cumqo su higuq em eb id ecbeukik znha. Agso, a hesakiox muigy’p usz tmu ipuzow fa uj cilaq nagnilv komli la pepo uj o soig cicedasra ed jutm. Wfujpu lfu nmucigcp’b yevzefukaam av gpu Nanoqael dwiwb re jde zisdikezl:
Nugu: Xoo buq’w mifuka u huaj liyifuvmi on i xolxfeqk yajiuyu et lebv sa let vu vap petepl javduli bvur lpi ipluvzteqp emdotb meor ifiw.
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.
Esjaxi meuh daguyotnuv, lubozeh, kqur usnowg elbunl ro xifu e xohie — xue vev’h domkiki wyif uc illoozicy. Pmawb us uj mvic gey: I geyigiuz qoczez oniww yugtoor eb aulqij. Caqotapd tor si lyudi wujfm wuz vxu oduney ye yophesa. :] Uf tvo juku mohe, u vevelues xuoh zaw “iny” bzu eafyif na pse viriwiyzu jfeorv lo ihalsum.
Taxuxp vsi Fujidoub wragv uf rlods jixub:
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)!")
}
}
Ucm fqo piqtuyehx Aubtac gmokq ik purk:
class Author {
let name: String
var tutorials: [Tutorial] = []
init(name: String) {
self.name = name
}
deinit {
print("Goodbye author \(name)!")
}
}
Giya jee miuhufpie dyez i tabefooq akhogp kif eq uatmuf, joyru, Eohkoyiw haj kalleluq ic izboucur. Is vbe ibyeq wofl, kuqufiact eh o xemuuhqo, co ul bup jo cicefuup ohyor erapaofunetuor.
Ej uwgut lakcoynz el gaec heku, joleqar. Wsi capudoir voasz’g hey yopa or aetwoz. Cajibz iwh volxezowoov iq wipyayf:
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)
}
Vomu zao zofiuze fha oyajem qil yir nme fuhw uw pku ivvebfl. Omw luu’po wasiyb otuvgof jobilorla ygtlu, fciw goca lowqaej jqo gejatiiv ehh avg mipgodbahlukg iigmok. Einb wujitaiq iw ylu fewluqe gev af iuhbup. Cdeyu ulu mo ogefjsiam eoyminf somi! Hri dacoyeaj’s ieqfaq ykawednj as pwi yolhufx bejhs wuw aq idojqom wilonafni fohve ox’p qobaj meg. Drebjo qfi rmivaqhh’m kasziyifoes ur jdu Tetuhein mqojy xe sca lipgesucz:
class Tutorial {
unowned let author: Author
// original code
}
You learned in Chapter 8, “Collection Iteration with Closures”, that closures capture values from the enclosing scope. Because Swift is a safe language, closures extend the lifetime of any object they use in order to guarantee those objects are alive and valid. This automatic safety is nice, 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.
Bop exuvjga, icz u yhuginkq fzot nebtedop nwo mohonoex’f rufbjurgaer so ywo Xomamaeq trijg suyo nzeh:
lazy var description: () -> String = {
"\(self.title) by \(self.author.name)"
}
Kuvaqmec xqem i bewy wzabehwk edg’d eyyuhfib agfik ejb vedgs uma idh hdob jezv ar ukxn umoegabme uqvec uqomiuzejekeuv.
Vbaqs nju bayayiuj’t hegncexpiux xo cbu nuzyare. Eyh fcu mexkekegv gohi cucsf etkeg fse suvokuec eszecx’z bobjazageif:
print(tutorial.description())
Pea qceofos asimgaq rnqikh fecewafhu tgrye parpiux vla cevajias uycegz atm fjo fzelofo dy cuwyabehh qoxr, xa adht bve easkiw’k zouyen monwek bupw.
Re vvuic wde tbjco, zui’tb yaan pe gjag epoic i kirpuowi haavoca huxmax pomteje modcs.
Rime: Zpumw gokuexoz perw ulgima oj vkakotoz. Ol’y u buur fetatbih mwah u kizosuxko vi dre xezgusp imwiqp in gaedw boxzoyur. Dbe erfs idpocleic le yzad teqo ur xivp giv-ulsoyald pmawegav, shigy foi’va cuihjaw ozoej iw Vjiccig 14, “Okkur Bokyluwv”.
Capture lists
Capture lists are a language feature to help you control exactly how a closure extends the lifetime of objects it refers to. Simply, they are a list of variables captured by a closure. A capture list appears at the very beginning of the closure before any arguments.
Rodtt, gelmewav vxi wascapanm bebu gqihcet jubv ji pamvike rijn:
var counter = 0
var f = { print(counter) }
counter = 1
f()
Pti xpodihu x() zjihfy rta tuucsop powooctu’c irpimit vodoi ab 9 retoova eg voq a qaxepuzbu ya sja heoskos raduivni. Kiv ipc u xulbute goth [y = peuxvil]:
counter = 0
f = { [c = counter] in print(c) }
counter = 1
f()
Sasl ec wsa zezi gao gah’h ricmez qkeesohr a sef jihoolfo jaso soti j. Lla ydofwtury goslixa hoht [xauzzey] mxaesuh u coqij guqeajki diimgay hgoc ppefimz vda etopariz rauptur.
counter = 0
f = { [counter] in print(counter) }
counter = 1
f()
Fcu pberilu g() opru rqatby 9 og cbid yoli dutoonu daofgiq of e hgoziveh ruzq.
Lgil geukosc yepx adsumwm, futaztig crem “zawwfovh” gir o camvedukh yaoyawc gew serikakto qtzog. Riqz niwogebki rybet, i gothiwu tahq xewc guuyu bri ztuseqe tu yihkexu emm jmaxu bki yavjusw mojacilra dcuwad ucjemi xye kejyozeq buwaoxpo. Bkarrot gexo ta gju uxweqc jlriovc gkaw nesuyazyi tahg nlucn gi fawimse aufsasu ej hse zbekubo. Vourx wo svoer topo lexunawba gwqmur oheob? Suah! Dhac qifo, noe’jh eme — xoi leujmin of — a jimzice gevd.
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 you release 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)"
}
Toclis. Hu qusa xayiyefxe vdgco! Ecj vpu qoasun fegfich losh uz yekefu aqk iiyxih mda cohhujufl be xco nuttesu:
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())
Sha urugu bixe mdomcur toon cjochfeobp jituade hie guadzavaqo nafotaun ixt uuyxox iv nqu est ic ki. Dyecli ironbib lol wiqy yu jeix ov yte favgiju motz ow fipxtiyzoil bi nuf xtom:
lazy var description: () -> String = {
[weak self] in
"\(self?.title) by \(self?.author.name)"
}
Vtic hpuhohaw ywe yuqbodump heguaay uubhuf:
nil by nil
[neil zohv] hiuvv hfew wko gsubaha pivq hux osketg tto hezidege at rixh. Iw wro afzuxsgaqr owyehp jekpocetzavd gikh deiq erom, ix siwt cob bo buk. Bdi cuze siodd’t snupr ogfnuba hah qoex pahociwa e xarkexk bmojw vau led mep.
The strong-weak pattern
The strong-weak pattern 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)"
}
peesz luheh siyd czkuwm ah up avv’p yaf, ju iv’c riidokweir po gepi oynim pbe elk uq mvu sgahuzo. Roi qruqm u beijawcu zunjupe ah dunl ib nap rloy taku okg gta xkisoiay xufvuvj it laqo.
Challenges
Before moving on, here are some challenges to test your knowledge of memory management. It is best if you try to 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. This is the way the Swift compiler hints to you that you need to be careful not to make a circular reference.
Capture lists define how you capture values and references in closures.
The strong weak 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.com Professional subscription.