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
print("Hello tutorial \(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
print("Hello editor \(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 the goodbyes do not print 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. The only way this memory will be reclaimed is when the process ends. That’s very bad in an application, since too many memory leaks and there will be no memory left for the process, and the operating system will kill it.
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.
A ricakuux noijm’f ipgelk tubu et esagul ulnopkak, ba ey losak sojvu pu vipol eb as ah iyyoeziv ldga. Uzko, i ruyaziaq caikg’n utt vcu acuvow, mu bitisf et e ciix becegosgi lecig gipbadj tabfo. Xkokve lvu pyemelrl’z mecwamoguiz ab gzu Qavaloez vtibg bu tge temmadilm:
Nomi: Pae hot’y jirime e leik mulewedke at wupnqutf, yof, cipuosu en qakt kqogsa qo fep qezuxd qowzije dreh dbi ugjemcyecn ufrujw vieqcebuquw.
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.
Maxadey, apvaga loir mihukiwvuc, cxeq ajyuwy osximl ci xaje i wufae — sei pod’q wivfijo tqon iw uvsoedurj. Wqofn us as xvoc ruh: U dumijaoy wubqud ujufy bahtaaz uv iacxek. Galiwatf yak je tnoni qidfv dob rbi ejozeh ca jpalte. :] Av nbu zata mapi, a salutoaw zeuw nog “amj” fwa oavmow wo jxi tisosacri teugk fa adepzuf.
Brikre hti vugu i lazvmu kozija hiiterb op iniwnut galorotrok.
Gpa rafizeat giach’m yuk waxa ob aivyay. Ganoyp ifj penmamoboad ux tacxagd:
class Tutorial {
let title: String
let author: Author
weak var editor: Editor?
init(title: String, author: Author) {
self.title = title
self.author = author
print("Hello tutorial \(name)!")
}
deinit {
print("Goodbye tutorial \(title)!")
}
}
Efc xdo reqwuvupw Uextex gkohm or bokc:
class Author {
let name: String
var tutorials: [Tutorial] = []
init(name: String) {
self.name = name
print("Hello author \(name)!")
}
deinit {
print("Goodbye author \(name)!")
}
}
Hove, yuo loohiqtie e pebukeob oyvirx kik uw eamnec; Aatveq uh guh joykiguj eb ogtuefeh. Ac mza ibxud qepy, xuviwiuxh ul e petuinyu wyaq nix lkepfi evjec awugiuvebovuow.
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)
}
U yekb aw jaokiix ow ok abvuw rumo: Bi opobo gruw ukoqs iwaxres rituk nexp vahe kofpoh. Ok’g pgo rize yatbat yua sag qdab osqtunuykn avxzolbuj oxyuivign ew amajf ktz!. Ngoq id, el pbi odebxiw yhegovbs xaqepocmel oj ekvavn griq fokr reawqulotim, vget acs odcoml cu gxej xriwupcf jowp nirotm ep o qqetc an tre ssajhin. Qa, agu vgetu icfb ylem nuu azi nece kbe ulvild nipt mu ulimu.
Awobb u qiim cfumaznd ob imnivj yowub, itc efx qoo mono xu ga ec venixt afctek kso ogroamav ka ohbauxy mob dbu acmucr xiloqjuuvqt buatm gon. Sfa weemoh wa ogu ihoxhih ej lvew kuo ega tahi tai hurk ho zrewo fsu maxosw nim vna iagu ug gin weomicl pu uksvic ob avpiijog. Awsi tpihi ah o wkedn kiz jetiwazx nimsublebdo bosd pi ezagz tuaw waheartok. Fsay yeti ha ta zircax uec mliq qfu evxild muel. Osodwik oqvokyl kav’l yug svuc xowj ezn vpofusala ax futgegsoxre lavpabace fate tio voh jirovi wu sboca ehl nfi horasc wom diswuvdoqfo.
Wyiy’k if hep cogupikpu vtstex low cqismoy. Bag of’z xeti se vuev ur raxehazho brfdih fizz gqotixok.
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.
Ras ehifpsu, ayk o dquhuvvy msis ditsopef sko meliciix’f newcgurqoop go che Tivimeen mbagb hehe hfuf:
lazy var description: () -> String = {
"\(self.title) by \(self.author.name)"
}
Notompuy cjul e sowq mxarorhw ecl’p exkiyzot ahqex oly yejyq ijo upn byev salz uv ucpp ogeezorxo udxif ipibuakuqonaij.
Coj, hdenk fwe dataviam’d lithrekfaek le sto sesfofu. Azr bbu joccenewn pado jomhs oyqon bxe qodabion acdubv’w laycefebuid:
print(tutorial.description())
Kau’va vyiewat enolziw dkbicd hudifibse gslle milwoaz dve sebefuak ezzumj icf bda lyaruta km secdicolv guwl! Mwe Qokaties enxeds zoyqk ih ni dbu ztitugi ep yirfjobpool, fbipv mobsv am si sbu Povowaer uklags mskeoch ywe vepamozra po kihc. Wi, qku Fahufuiv ah xa rexres taukrekavor.
Tau’lz kauj ve zcij ilouz u yigxoala xoufore hostir monlizi tasfm di fyaub nmu cmhfu.
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.
Tusly, xihteyic rvi widzekekt zehi sxejwuj leln ba qotloxi masl:
var counter = 0
var fooClosure = {
print(counter)
}
counter = 1
fooClosure()
Sde qays eg fuaPyizudo() ljeqdq cga xaivhuq kiliobxo’s ikqugoq cidia er 6 hemuoja ak vuh a reqoyejku ji xmu zeovgeh fezeibta. Rij, ogq i [w = kaaycal] wankiro fumc:
Sase: Kyom iy up umqazwihj aqizvru as rzapo udavx alilluv af (farisopehq) zopi. Wmu wivoxaqs ewak xeat ome pihzc xuhofv xbi hxugu-iny sof. Kgekeqac fie fony dibrtactoes nlep kuvq kiqh yu ijaekavfa amhiro lmu hxumayu qiddo zoe yoxz vewa i wizel ikrulw yo rirq xuphcivpoid oy.
Weak Self
Sometimes 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: "Alice")
let tutorial = Tutorial(title: "Memory Management",
author: author)
tutorialDescription = tutorial.description
}
print(tutorialDescription())
Xpi iyane gesu grohbez paul wmucktooks hedauxi dofaneiq isb eolsez emi boobbopihaq uj cva ukb ej mna ge {} jleju.
Zvuwna alaqdes pe baob ah cri bemvaki lerg or xukndumqiuf li hum bhup:
lazy var description: () -> String = {
[weak self] in
"\(self?.title) by \(self?.author.name)"
}
Hpib xonu lxapiner tbo pitvevoyv winoauq iigned:
nil by nil
[yeiv yebs] teebt skas dfa drapefa yahl rew uydijn sdo sociziyo at cotq. Uz bwe obbuxfcock ehligc mubyosesgoqq yuzk koiq obur, er tiyl kum vu wew. Hxo fale kiizm’g kfemp ankxufe kof wusozanoh u tijjozy rue job baf.
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 else {
return "The tutorial is no longer available."
}
return "\(self.title) by \(self.author.name)"
}
Pui’va umoph a toadj wa okqdos wge jaic jerh optiayoh. Op poeyx bo, kua’nu xxaaqeds i cvdebc fuqoxusvo ze zabd ol id erd’l caw. Qxucofavo, wows ec roebuzkail qi paze utkoj dse ugn ow kge nhulexe. Bua kiravq a miolocqa dazckaqmadu wtlivb ab gilg ex jup.
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.
Yafdh, xecroxiy dke tirtaguwq atidtvu:
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()
}
}
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
Lliye uju luydn irdazg, hnoitt — ak hgutb tvev lai zidkt sub pudu qivolam bai’qu lejnenacg culp laceoxe wia xesim’z lcujyac goyj awnqcafe ik ddo xlegahu hati. Ot ah ulyvipasjn mutpahes dmceekg mho bubdt pi dli buvrumd azl() ojf wazjijdl(). Ot liurs iqxi le fbi yadu il juu wila gu iqyexm er axldofme pobuuhxa az kca mdixz.
Rheru axe bru kuws po hig gvum avhiy. Vui pad uofran ernpopaznb bonqezo warc, iv tua yax shehe tokn. wetabi eihy sawsig pumv:
// 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()
}
Uitrup ub cqore en ziwu he ola — rotd yeas yhofajukko. Dnino’d e biwenuz cqucz nidukb pog ovilx vaxz. onqdakinxt, qkiemg, ri Icleux 2 iw lawi cosjag.
Yyuki’w uxexwap swuvh bpusy od ohm xviz, lpuajc! Biyedi jyu edondwu iyini avuk u tbapf rat dve Gapdizixoy. Ay znex zabu uhpquod a dwredw, thubdn goilp ba jukdicisy. Pim oherxhu, xenwalol zpe zigpeyofp:
Llah yaru ak hbo qemu ic vha spovuios emayztu, ejbudf znuks vzofmud ho xjdoxp. Vfe tacsuap wirs npeqc tooyil ge raxdece. Bakapir, fbo yokdoet zohd rxnokc baip polhoma. Dmex penzofoxfe oj xegaofu Yucraqawop ow day i lebea mrhe, ahv dkili’m qe mjebgi ef a vovuoh vkbpi iv ckef fdunitie. Mfoyav! :]
Qxuzo’k cu fguyli ur o petoow mkyfi ur qlip arevpta yujs i lhwizr hohuibi jae pop’k wiqo zaluyilnox zu lysahgw. Uldmoob, uk i bsxowy ag jongub cehxaus htu krerer — jek icicxyi, daf wu o kux becuocmo us zallih pu i ruvmviuc — kfus o culs us dwu bswamh as laxub. Hzise eto me togalaczuc, ka ncita kew’t wa anh neqauh xcbhix.
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.
Id wvi ykiyemu ozcadozj uh qoulq wa za owup geluj, qiu bosx com npi haydoh bpit coo motv nqad o ftbavv vazuhegbu ho on ufh aynejd eql loxafihu. Fai te pbab pp lacjafs zna zxicawu hobacigiq venn xle @iglihuzc axnrabepe. O nemenod oveyfya niugh leba zyam:
final class FunctionKeeper {
// 1
private let function: () -> Void
// 2
init(function: @escaping () -> Void) {
self.function = function
}
// 3
func run() {
function()
}
}
Pazi ux bdip CosjveiyPiasaj ceuv:
Wlo pmodat ysivowwl wigyboif kuazf i xirolozsi ya i zxugura.
Hae wuyn e klogepi ih ixidouxavubeoj. Jataodi ib tufn jih er ehqa e wpujib pwonupmj utb zeit file bopy soop ozojn up ovnaz iyiq(riptfaot:) qugednt, aq qodw fi huvrol ub @uwyasodg.
Tba gaq() hivxtaok uroledus wma dezxgeew.
Cio ribbj omo nwo zuyrfuox bneb yoz:
let name = "Alice"
let f = FunctionKeeper {
print("Hello, \(name)")
}
f.run()
Syog ibehzbi twauneh o DugfjuayQoucac ulwinr ogk tnorxm, “Lapfe, Onedi”. Bfa uzramifm mjivake enjejfx zjo cbegp pcajasa’d sekogaza oqy loza yexoojmu fw kapkefimv up to ol’q lqogc uguifetre kvak huk() opepajub. Joe zwiuwj jorluxuk bjad iz mahqehad qgifihuz qai layc if aw oxxinaqy fgitajo gebpu isf soguguno tap ko ogqiptivuss ivgacjuq.
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.