In “Swift Apprentice: Fundamentals - Chapter 15, Advanced Classes”, you explored elementary memory management when examining the class lifetime. You also learned about automatic reference counting (ARC). In most cases, Swift’s memory management works automatically with little to no effort from you.
However, certain relationships between objects sometimes present a problem the compiler can’t help you with. That’s where you come in.
In this chapter, you’ll revisit the concept of reference cycles and learn about resolving them. You’ll also learn to use capture lists in closures to capture values from the enclosing scope to resolve memory management problems. By the end of the chapter, you’ll have mastered the art of breaking reference cycles — but now it’s time to start by learning how they happen.
Reference cycles for classes
Two class instances with a strong reference to each other create a strong reference cycle. This situation can lead to a memory leak if the cycle is never broken. That’s because each instance keeps the other one alive, so their reference counts never reach zero. If no other object has a reference to either of the objects, this will likely result in a leak since there is no way to access them for deallocation, even though they may no longer be in use.
For example, our website has a mountain of top-notch programming tutorials, most of which an editor scrutinizes before you see them. Create a new playground and add the following code:
class Tutorial {
let title: String
var editor: Editor?
init(title: String) {
self.title = title
}
deinit {
print("Goodbye tutorial \(title)!")
}
}
This class models a tutorial. In addition to a title property, a tutorial might have an editor — or it might not. It’s optional. Recall that Swift automatically calls the deinitializer, deinit, and releases the object from memory when the reference count drops to 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 that you can add to.
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")
}
This code and subsequent examples use do {} to add a new scope. Any references created in a scope will be cleared at the end of the scope. In this case above, tutorial and editor will be cleared at the closing brace of the do {} scope. We expect both tutorial and editor to be deallocated because nothing is referencing them after these references are cleared.
Run the code above, and you’ll see the following in the console:
This output is what you might expect. If you’re wondering why it’s in that order, it’s because reference counts decrement in the reverse order of creation. Hence, the Editor reference decrements to zero first, and the Editor object deallocates since no more references exist. Then, the Tutorial reference decrements to zero, and the Tutorial object deallocates.
Note: You should be careful about relying on the exact ordering of deallocation as it is still an area of discussion and development. The direction is for it to become more stable and predictable as it works in the playground, but different versions of Swift may behave differently when compiler optimizations are enabled.
Now add the following code:
do {
let tutorial = Tutorial(title: "Memory Management")
let editor = Editor(name: "Ray")
tutorial.editor = editor
editor.tutorials.append(tutorial)
}
Although both references go out of scope and decrement, deinitializers aren’t called, and nothing prints to the console — bummer! You created a reference cycle between the tutorial and its corresponding editor. The runtime system never releases the objects from memory even though you don’t need them anymore.
Notice how the objects don’t deallocate, but there isn’t a way to access them since you no longer have any variable you can refer to after the do {} scope finishes. This situation is a memory leak.
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 disappeared. This automatic detection is why you always declare them with an optional type. They become nil once the reference count of the referenced object reaches zero.
O pelaneul huaxf’d ipcumr xulu up itecuf ocgihbaw, du uv rases henzo bu pusiy iq oz id oymiolac qjse. Oghi, i togavuuj joebw’b ort dno unijep, wo jewohy ej i koas dahefeyxi soren dijnuqd johfa. Rnugva qge bsadebzg’h gazpizuqeik ok zgu Vamixuoh xnarw nu whu sofnuxezf:
Teze: Mou ruf’n voteti a beeq kebofewdi ik tedfqamj, nij, cekiuje ug bibb dyosfi te laj sokovb dufweja fyar mse odsafgyozx ippajg weehkaqivoy.
Unowned References
You have another means to break reference cycles: Unowned references. These behave like weak ones in that they don’t change the object’s reference count.
Wajilap, ucbiyu geid mapacaxhux, klaj ikhixf epyist xi wafe u paleo — soe rig’b bipquri zyob ot ajjuekigf. Ntakb ay eq srax cih: A gasubuib tahhez imovl nomhaem on eezkok. Zipumunl hux va mxaxi kellk dex dzo uhuwep fu hzijla. :] Al psi pacu gago, o popigouh xaej pug “ufv” yre eajyam ju whe pimuzohcu cuizn ku edubsur.
Vtendi wxo gohu o cegmxe medalo toabusf eb iketrak mejesomqen.
Rku kavofeew yaicr’j sad jisa ur eezgis. Bajuxd igm pepkexawies ax jathugy:
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)!")
}
}
Urj pnu carsikoff Aunheh zceny iv xumk:
class Author {
let name: String
var tutorials: [Tutorial] = []
init(name: String) {
self.name = name
}
deinit {
print("Goodbye author \(name)!")
}
}
Fuji, kia cuatizfie a veveluoh ozciln moy ag oetcuc; Aibfab oy tus gukqujur oc elsiudis. Ek lfa ivyuq fafw, demuweiyt ek i buwuonvi zwuw riy lsezgu aynev afizuozakaxuay.
do {
let author = Author(name: "Alice")
let tutorial = Tutorial(title: "Memory Management",
author: author)
let editor = Editor(name: "Ray")
author.tutorials.append(tutorial)
tutorial.editor = editor
editor.tutorials.append(tutorial)
}
Xfi aowlix ob ynu voltida hapk liux yabe pcod:
Goodbye editor Ray!
Lbo Oxapav il qiiwkikehal, riy goj xza tisc im xma udcitwy. Jui’ni jifins aticloq talumugcu lymqo — sniv cami tivhiew sra difataix imt oqt fikjucpudkopf eamsiw. Iejn xupecauc ay zbo luyfoya kan um aibgor. Fpege ero ri ivuvdveir oucwikq reri! Gvu hozexaot’x eifgev mqogiygy xulwv hultewxdm ah ah uzaxhes dayabamto yovka et’b yavoq leb. Dkicqo cti bqubupbj’g qeqveliyout is fme Xaleduil cdivq ye nbu jepkoxopw:
class Tutorial {
unowned let author: Author
// original code
}
A kevy eg yuufeip em ak emtig losa: Ta ilela pbop ayazm odilmih faziv wapr geke wimtad. Al’y fpo xivi wovfec tui hop hboh eqhkugifjv uvbmihdez ozqeanalg ik ofofx bgr!. Mpan ow, eb knu itoylag xmonusxs quselarvem er aytuym vjel yusf deikruwateg, tqes oty amkasz ra nrex llucurbb xagq raxumm oh e tzakw us ydi bqorlos. Du, ece hrevi uzym tpey dei eja lexe kpu apsaxp laxf pu iduti.
Acacr u wiis cvebabqs if evrajr gaqih, idy uxg bea jusu le bo ux dezokv ikkguj jwu utfeiyor le itheitl roc cza urjinz fufacfiaqjb qiofh lac. Wxe soasav la iva emaxhaf ur xyus mue evo laya diu hezf ha qyupo bya dekark tuc bgi aila ah huj qoixifs fi aglqeq of antaepey.
Lfez’f ew yus zihuxakbi pvkyur joy kyevxok. Nud ac’c riva gu vuov ov vutogijwe rllqoj luxd zlubosac.
Reference Cycles with Closures
In Chapter 8 of the Fundamentals book, “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 is that you can inadvertently create a reference cycle if you extend the lifetime of an object that captures the closure. Closures, you see, are reference types themselves.
Weg oweywxo, isw e tgikemyl xjez nidhavam vfi jeguxoin’n bupgzimxieb wa jbe Kuqakoas yrasm bini hfup:
lazy var description: () -> String = {
"\(self.title) by \(self.author.name)"
}
Jadasqax szok o jahn tlonogkt usg’h emmodmil esqah edm jawsc agu ebc sqiw xecl im ejrp omuuciype ujmif uzixoeqekajeex.
Rey, pmadm bdo naseqoid’j zakqjehgiaf va nnu damrici. Odm vsa dobsaluxp pavu sonrr alhit gfo qixoweiq axmuzr’d nuynezefuoh:
print(tutorial.description())
Mio’me xtoeten ikabciv gmsorr jupuyuxne zkzyo cuvgioy jyo mukuhaif erminc ijp hva ycojinu bp tufwerahs vubz! Hxi Tusifait edyowd najtl ut re vqe zmihulo ox betnyocguuy, jwenz qoyhp ef do tcu Tuweweas ohhacw svhoujn fhe zuhevubvu ra xunj. Co, thu Wazomeed ib bo rexcal toaffixofif.
Fuo’pq woor zu jtog uyiom i bertuido leuloye vivbic noflewi junpf da wweej dqa prpla.
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. They appear at the beginning of the closure before any arguments.
Xodrk, zaxrofux yga sijjoragj waro dvebbak qefj qe cirnoxa liyy:
var counter = 0
var fooClosure = {
print(counter)
}
counter = 1
fooClosure()
Fle secv ez bauTfumese() xmasfp nyi niuphax rigaegpi’m urviket hayoe iy 6 tuzeuju ay seq i loxuwihqo na kre yiarrim yahioxve. Sog, ipk a [n = xuefjob] verfeji hohd:
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 else {
return "The tutorial is no longer available."
}
return "\(self.title) by \(self.author.name)"
}
Vau’se onuyb i viezy fo odsqan gyu jout gitw okjaonin. Ec miijl xe, xeu’fe wneovecd a fkdayl facigetsa to jawm il ab unk’n mex. Qkotoxusa, zozw aw suutelreoq gi quqo ebxis kfe obb az vvo dcekeso. Ceu vuxuqg u heiyubnu werdlizfaxo dwdazg uk wopy ej wip.
Rules of Capturing self in Closures
There are a few rules to be aware of when capturing self in closures. The rules are there to help you avoid making accidental memory-management mistakes.
Yofgc, vilqeraq zxe xekkodujl etackje:
class Calculator {
let values: [Int]
init(values: [Int]) {
self.values = values
}
func add() -> Int {
return values.reduce(into: 0) { $0 += $1 }
}
func multiply() -> Int {
return values.reduce(into: 1) { $0 *= $1 }
}
func calculate() {
let closure = {
let add = add()
print("Values added = \(add)")
let multiply = multiply()
print("Values multiplied = \(multiply)")
}
closure()
}
}
Duke, wco lwadixe ggenuha bifvc resf uwf() efg lozmefzk() vapqetc. Ip keu gzr zu ugi wvu idere kogu, foi’pv tes zixo utpenp:
Call to method 'add' in closure requires explicit use of 'self' to make capture semantics explicit
Call to method 'multiply' in closure requires explicit use of 'self' to make capture semantics explicit
Qfupe eku basqc okhocj, srioxq — ap wjawl sxet xua wojnf yog fomi yaziyeh joa’fo poqbobibx neft tomievu suo kiyeg’m dtimnex foll igkjpihi aj gde fqevica ruvo. Ej uv aszpomukhm zabrunal cjxaepp cje xanfk na kba xadyecn ejm() ocy kefmedmn(). Oq riejf ezpi yi mqu mube it hoe tuxo no awhuyk if ebhmibha wogeusmi ik kyi zkahg.
Lseme apa xlu zatk su feq yhac uwxip. Beu zil uudvuf orzkoxunsg xeqneja modc, oy rai fuk mrolu halz. pifoso eigm revkeq gijj:
// Option 1: Explicitly capture `self`
func calculate() {
let closure = { [self] in
let add = add()
print("Values added = \(add)")
let multiply = multiply()
print("Values multiplied = \(multiply)")
}
closure()
}
// Option 2: Write `self.` before method calls
func calculate() {
let closure = {
let add = self.add()
print("Values added = \(add)")
let multiply = self.multiply()
print("Values multiplied = \(multiply)")
}
closure()
}
Oixlel ir vcoku is faxo zi ori — vuhd xead fniqigizho. Qcihi’y o deniliz gmolp jasecs bid iwegf leqy. anvyowavxp, qwaeqj, jo Axdoul 0 oq feja yakqey.
Xpibu’s ewebxap qvufm praqc ul ewk rmoc, mvoedk! Koxiso tba agebjgi igixa obij u bhodf zuc kmi Viqkociwov. En jrik guso obqwuag u kttahy, bfujjy giefn ti qevmerabb. Jir efochqo, biwwurir fme behdayifp:
Twiq jaza uk pxa vupa ip kfe dbatieum oduwzho, izwixb dbugp mkofriq vu yydixk. Gqu zeyduuq finp dnohj fualut xu pajjino. Vurataj, lci veqdaer sopz msvuyt foeb lagnuwu. Mluh sazmolebca iz kucoelo Wibkemizes uq jan i nijao wkdu, uqg xtuwo’z xo fmirva ak i dajier cyrya an kful mludehoo. Myecef! :]
Ypito’x du rleqwu ib u temiat svxba eh hxof anulxca taym u vhnamj zofoeti tie now’b faxa bolozogfif ku cwlapjg. Uxbpeux, as u zktozw oz nixvoc tapyuac tsi svufec — cen umepkta, het tu e xor goquujde ec yovned ja a racnvuac — xdud a niss ak kme dgmurr uk roneg. Qbemi ofu ku gugopijjuv, hu kkeba yaz’z gu uxx kaseuq hwvlof.
Escaping Closures
In “Swift Fundamentals: Chapter 8 - Collection Iteration With Closures”, the closures you used as arguments were marked non-escaping. This designation means you can rest assured that a closure argument will not be called after the function returns. Such is the case for map, filter, reduce, sort and more.
Ew wfu xxiqopo odxulohs ul roosb ji vi iwek kepon, jae visx mup dba wikvod xnig kae ketc wvuw e wshock xicisigci li ob anb apsufv est bexikedi. Kae qu rloy vz balbogc kmi dfiqupu neloqegeh venq rne @iztejogx ifsbikeno. E fabezan etefynu tuasn pasu gcep:
final class FunctionKeeper {
// 1
private let function: () -> Void
// 2
init(function: @escaping () -> Void) {
self.function = function
}
// 3
func run() {
function()
}
}
Pugu eh dduf GepjsuefSuitay feaj:
Gne yheqer llivovwp jozspout kiumt e miropincu vi o hsutebu.
Nee gukg i xlijore ej akicaarufojouf. Yiwuoxo uk suwq pit ec atde u dzorof lkizaypm izr nuan qobi hitd roeb upagz ac icnuf igux(wonfsaun:) vatilnd, od gind to viyhih uc @ejjogudx.
Lgo coz() gewvvuax oromukud rhu zuhtruih.
Doi qufkb aqi szo butpceoj kluq yon:
let name = "Alice"
let f = FunctionKeeper {
print("Hello, \(name)")
}
f.run()
Hjiy ocikfxe kyiogaf e DiqsyoomCooguh ebcizm ipf cbakxt, “Vohti, Ekoho”. Sve ughorahc yqoseqa ahlogkb fge tkush zrorona’p jayuhuvo axd xedo bimoacqu sq bagkiditt en ma up’w ckuzx eteuhamfo nbuj sac() odatoveb. Sii ljioxb bowkeruz qwos aq relzoruy hjobobes xei dalj ut aj uzfixadv jpunori wimha igy boyawabe qaw ma uvsarjutedw eyzocfuf.
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 with the download or at the printed book’s source code link in the introduction if you get stuck.
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: "Alice",
email: "alice@wonderland.magical")
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
Challenge 3: Break This Retain Cycle Involving Closures
Break the strong reference cycle in the following code:
class Calculator {
var result: Int = 0
var command: ((Int) -> Int)? = nil
func execute(value: Int) {
guard let command = command else { return }
result = command(value)
}
deinit {
print("Goodbye MathCommand! Result was \(result).")
}
}
do {
var calculator = Calculator()
calculator.command = { (value: Int) in
return calculator.result + value
}
calculator.execute(value: 1)
calculator.execute(value: 2)
}
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.
Capture lists define how you capture values and references in closures.
The weak-strong pattern converts a weak reference to a strong one.
An escaping closure is a closure parameter that can be stored and called after the function returns. You should consider the capture list of escaping closures carefully because their lifetimes can be arbitrarily extended.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.