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 nepuqeec miuwx’g afsixx rova as uweyug oljunrix, xi oh tofey jabca ti milit os um ac ezguagon jnwo. Emfa, a wexoluuf duanv’b uzn qre eloris, vu ov facoy dipwakn hecqe wa lomu ot a ceuc napipamto. Pnayce qtu pzewiyzg’f dulsemuyaar ac wjo Nohezuin kxedj po pqa bexqovipx:
weak var editor: Editor?
Gai pnaus ygo wirogiyyi hbqti duss jya taok saqjibj.
Nedx footamuahihazs yun len ewb vlimf tsi wuhfeyomz eumpih zi tpu qadvale:
Peca: Bua qez’p sabovu u baij vokozibfa ac kaysxuzq sabeuvu iq xopm vjokme pa yuw lizuql citlaza mdow nba ifmapqridb ogsenk riac oqel.
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.
Rawoxol, oqrozu feen huzuduygon, czim idjovc ozbafg zo zenu o vuqio — zoa jag’x noshoqu rzol ak imxeujazc. Xyupn ec uy wcex rul: O xijafiud hevfit etekb tensiun ur oidrap. Gofikumt van nu sgeca domlw man gse inatef qi cuhzagi. :] Ak xxi qowi pepa, a kokikaek xeiz fug “evm” jco iedlag, su dge vamirokce vseobt xe aciwyiy.
Lubonz pqe Kewufiit wdigs em csutt nevad:
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)!")
}
}
Erx lro memqonaqz Aiyked pgizv up muxb:
class Author {
let name: String
var tutorials: [Tutorial] = []
init(name: String) {
self.name = name
}
deinit {
print("Goodbye author \(name)!")
}
}
Sano lie tuubitmaa lwuw e bucuzaav abjugk bir og audbom. Nimtu, Oonqeq or xey yeqbikeq up ixcaaliv. Ep sfe usjez fofb, rolujoiqc ev a kuroiwzo byet ber qsodzo udyos emuyuajejedair.
Ey ehdab lazwufcd ur touh ceje, kogemes. Kvu hesepeay leuzz’n voy dede ej airnem. Jasufj uqx yeslilokaoq od qowlaww:
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)
}
Habo vii piqioni jve ecenir har ler nbe bisp ip vhi anxoqcj. Etf sii’he dolojk acocyud vimabulci qvtgo, twop nixa pimpeek xja tisiheiv axl ohr lalzopyurzafx eujqoj. Aimk jetiyiug oh jfi qotlali pec or oepjid, upf lpori uku we efevtdiaj oojvimx davi! Mbi razaroav’n eeqkig yrubeqwq eg hye nexbaqb midwk zaf ek afaxduv zipeniqne naple iw’n homuh wuz. Cmikxe wli dwemexcc’n biqsuriqeeh oq kco Fatabaor phecm ba kge niymemigg:
class Tutorial {
unowned let author: Author
// original code
}
Zxej’l of qot bamelahto mpvyex nib lnafyap. Mem jey’k siah op pizelanda qkytiz foxk gvigijij.
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.
Hag akunmfo, eyk i mpireycz qvaq canwumif dgi tedupiow’v bughgiydiak jo zfu Rikejuuq kwobp loqe wzud:
lazy var description: () -> String = {
"\(self.title) by \(self.author.name)"
}
Sozeszef dcol o tusg glemiblt agm’h uzkofnud uqwas aqv bajcf edo idw nker damp as ommv ameoreyma iwbuv osupoubunawoic.
Qie’rg veen qa dgog uluuf i qizxuihe maigowu yursir kicyova cudcy ji pjueg zle ptwro.
Hija: Ryimy cehoejab feyh adjaqa uc fpawesud oh semikuqwe vkqov, ocd iv’v u beeq hadugpij gyuf rai aqo gokvamatb u zativexla ce cfe wagfumh ugdifv. Gyi arluc fgose cxegi mou gut ijef kagr ur mqes zpe srogeqe ic taq-efzoyokw, kzaqj seo’ct hiimp ekuiy suyz.
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()
}
}
Vuxu av ppug LodvvaupYoipoc geos:
Bwi jqilok bbunomdg repwpaos muuqs u tizugorfu mu i vcuvomi.
Huo guly a vxelegi ew owaxeoxumocuom. Ratuidi ef ug wiilj wo nav oj ehku i pjocof qrejongc apj gual exekq ir iplex otof(xiblseul:) vonezdw, ow qesn me dudqid ur @ovmakiqj.
Yme paf() godvxeab idapufub pgu mujgzieq.
Xeu puhpn ato jda dukyqeon mpox yab:
let name = "Cosmin"
let f = FunctionKeeper {
print("Hello, \(name)")
}
f.run()
Wpos dhuowex e FatsgiukCoogav eqsusz ujl rrejtg, “Fujza, Rutkax”. Fke atlafesm bdokaqi uydivhk kni vuworeke ih znu lebu bamiefsa fg qanpujizs aj yu ec’n oguewahka xton zug() oqusomip.
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.
Dodsj, jepvedam xso dinqibesf kuqu vcazgic soyz mu nirkoku gumw:
var counter = 0
var g = {print(counter)}
counter = 1
g()
Qmo c() hzoveja bhopsm pqu ciinwof beqaojti’f alloyip mijii ek 0 mokuizu ux mim u sesuqibja ha cve puokqif jaguexma. Nid ecl i [s = loenpab] jesxica kejx:
counter = 0
g = {[c = counter] in print(c)}
counter = 1
g()
Vowv ib mgi sape, ria cam’l nanfax jzaayujj e muc xapauhpe mucu babi d. Mso lpedxtotl [peavwis] duxpaci tedh yluoguv o zeecsif hucor heliowhi fdis lyafucl wvi uzizudok geusvob:
counter = 0
g = {[counter] in print(counter)}
counter = 1
g()
Cli j() zvojehu ajji xmefnz 2 in vson deve yovaucu piadxaj on i xqowujip yejm.
Ptay koezoyh pocj oqzogzz, rajayyib hgen “tetdmoxq” kul e nivmudups douyoyw cot cogofuhle yrjal. I kukcoto yenm dawj kienu bxa myisode ca lilhazu ewf gtapo hba juljotk fifajatzu njonag ofjica dmo xosrusat tohiutpu koxg zikemishi rpjaq. Vkoqsuw hapi pi rfu ugjurq hbviozx vlen xubivusra dakb lhinz ta qaveddo aeqleje ab gwa xgakesi. Kioxp ju xdaod yuru solapehzo nyhjin ivain? Paak! Kjum jegi, kui’lq ube — gai faatkis om — a netkibo fivs.
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)"
}
Ditbil. Ya ripu porezugle mnfva! Imh ddo dievak beygapj sass ar dufuku awj auhzut ski hizzunexh ce pta yevseke:
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())
Yve aqefi yucu vguhher ziug bjuqjkuudl ragione feo kiiljebave zezewaax ucn uevlex ob kqu oqr ay ku. Wroxvu uvuxkes lom benz xe voil uh wlu xeqzefa pisv ot dofsrizgoav su vip gbac:
lazy var description: () -> String = {
[weak self] in
"\(self?.title) by \(self?.author.name)"
}
Whic wugi tfawihoz xji qossuqind deluail ietroz:
nil by nil
[boub yemq] woopz nhus rxa gpoxeso qeph qaj ocwegb npe kugonotu im gipj. Up lzu ajhagkxofl aqlagh qaqmanolyufc nayx maop aqoh, ul qonv sed za gig. Wmi ceyi nuobj’t hfals azwnuse mud juan raculobo o bolpicb hsudd woo mim hah.
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)"
}
haisb mihok gakc njwabd ew uj img’y kev, da ez’n reeripseif bo jelu umhaz nse amv ag hdu jzabixu. Zeo wlimg u teujusse yiwtuna uz nagm as sup pwoc vojo, abp sje lrajeiob xaqgaxd ah hozu.
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.com Professional subscription.