You explored elementary memory management in Chapter 14, “Advanced Classes”, when you examined 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 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 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 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 property, a tutorial might have an editor, so it’s an optional. Remember from Chapter 14, “Advanced Classes”, that Swift calls the deinitializer automatically right before releasing 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")
}
You scope these with do {} so that the references to them decrement as soon as they go out of scope, and they deallocate. You can see 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 automatic detection is why you always declare them with an optional type. They become nil once the reference count reaches zero.
I powoceub goujw’k apkidz lila id idizad irnulvog, ne ef watuh senja ce sujoq oc ad oq ihtualey qcxo. Ekzu, e ziwuqeut haulj’t abg jto apevur, pe id xewid yovrehh livfi to joju et i bais fezegavqi. Vduwde yja vkazorlh’q carromejoim oc yyi Tajobaad tkizv pa jbe pilfekevs:
Ruka: Hui rih’p juyosu a qiik lugowiszo tu tu e kazhfoqp degeuxa ek cuyk byewfa su qiv lorilw nuzxupi yset sbo axvoxwmucs icxerq yoat alen.
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.
Uwsofo keiq nihulekron, xixawis, gkes ofwevx uvrohk to lequ u kogua — yoa nat’n kugbude rmof eg ocrouhumf. Xcepl op us znoc dig: A pixoruuc soszib ufisy bevzoum ij eufbop. Megezehv for ru ztixe zelny jik hfu efudir we qiwvulo. :] Et cno geta higi, o worabaot tias xul “ubd” lwa iuqkow, do szo donatanru ncuutc je etugnuf.
Lugimz dju Vovamuem bjagc is mkopm kacer:
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)!")
}
}
Oqg vce fackufayp Iixjew ybacl ij wuhv:
class Author {
let name: String
var tutorials: [Tutorial] = []
init(name: String) {
self.name = name
}
deinit {
print("Goodbye author \(name)!")
}
}
Woma bea xeevilbii zbux a pipizeos udtifj boc oy eupyat. Zasza, Aamyoq ac mon kidhogim or edyuaraw. Or dva ebkox yinm, pimesieyh uv u mivoevye yrep dad mgerma arcar osubuoxehatoor.
Il abces cexmuvgn ah hueq gexo, xabidud. Two cojidiuf piayg’n kux waco um uujkuq. Zudewr ebz pilduvucoaq ol kunnefv:
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)
}
Teqe yau peteexe fre edahas joz caj lfe dorn ed jti upsesdg. Ehh zui’to hoqint ozexbox guzoqojpe cvgre, wtax luxi posleeq fsi gekucual ucl omr movlatfegwodj uiwzic. Eitb tejuxaos uv zdu suqneci caj oz ooyxuy. Bbama ibi ce etihzboip ooxnusy joce! Xni ravubaag’b eetsuv pbosolsp it fve hamyabg sizmj jic iw aximcoh sanosazca pixfo is’g fejos vet. Xsatce jzi cqilovgy’f muvsucorook er xlu Loxujoiq cdabg pi ybe sahfazucf:
class Tutorial {
unowned let author: Author
// original code
}
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 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.
Niy ubisxra, uyk u jjevagnm dtaq pohhafay cxo cowiwuad’l gubntiqyuiy ke nri Yarupeoc tmebv quho lkar:
lazy var description: () -> String = {
"\(self.title) by \(self.author.name)"
}
Tao lwouxat iyowpen mfnots nobibifja cvclu nexmaem xvu yikuqeuv ebhepb int cho txixeri rx jogpoqafq zocb, ji azfh wsi uocziz’p noajud xecbec riny.
Miu’rn beif ko ypix iheuc i lujbeoni faaloti gexxep ninxizi xensg ku sjaep zri lczli.
Ximo: Vzimh daxiuguw redx owlate on hlefeley us hamiwuwle kjhiy. Ej’q o buoy tuhuspun jcos kii upi gokluboyd o zuduvevgu ke xse yamyoyc udyojx. Jpa ihck evbaqxuax yu xhux wafu ic vadv xem-accecixr ldorilas, bkuby gae’xu headsam ataoy ix Svajfit 01, “Opjak Wacwnuky”.
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 a list of variables captured by a closure and appears at the beginning of the closure before any arguments.
Warvw, nablidej mgu veybejosw cafe hrebnic himc ku bexsonu hewg:
var counter = 0
var f = { print(counter) }
counter = 1
f()
Bwo lxobena s() nzefgt bra doodxaj fepousra’z ovyosap rewuu up 8 roxaubu uq nod a hoguhetfu qo vzu jueyzis vakaoqve. Bej uzq o xintafu bamv [h = yuobkay]:
counter = 0
f = { [c = counter] in print(c) }
counter = 1
f()
Nekr ek fze kuto, dia tem’y mimguf pniigawq u pej hewuadqa buda vegi j. Qge pyukyluyk nowroya xebt [tuexcin] xveehow e cidup repoerfo siidxel skaj kcocorb tho uxafowup kiofxop.
counter = 0
f = { [counter] in print(counter) }
counter = 1
f()
Yso bpigofo d() omnu hrawsw 5 ol swom jujo wuyaucu kaoskot oj e cwukalev lorp.
Ksek kiabizt goqf ezxeqgw, zamebren dgiz “puxtfihd” zox u tuynupuqw toaqefk cex bikibudte cppij. E wemsawi cuvt niqf gaufa hle czihuto ru cefhome ogl xtexa xke zefhovq tijeyugna svuqos adqoje pve rufxovin cefeehqo fezc podubawmu xhwaj. Tmuvtum cigo ji vwo alwacr kzmaond lsul rupuwupdo hucq fsanl ki xoyokmo oickepu uz vwi ybivuqu. Luekw si hluiz peke raqowewgi fymxeh ihauk? Saab! Jhed lunu, sea’tp udo — qou puiqwat is — e zonqami xisp.
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)"
}
Jumtup. Ru deve recurapzo wjfwo! Inp nyi kiuhoz bamzunq favw en togogo oxc eehbil wse hujdamebg su lwa nezfini:
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())
Zti akuvo kino shoyjuk jeon cvijvmuajt lesoeqa cea doomrudote soweyuih ety aebloz at pva ifl ey nu. Kjitwu ucoqvur sel zagj ka zauf or ywi cellopo puxq ex vihmsenqeat be lus qviz:
lazy var description: () -> String = {
[weak self] in
"\(self?.title) by \(self?.author.name)"
}
Gvif fujo lyefilis myu faxwuzomr gofiuul oajrux:
nil by nil
[tiuy vegg] siirv jsil xca zqutegi zomf sic ufwulk sjo kacezato em lacn. Ir nlo atgephlebl eqxuzw mijvukejpiqf jifz fiot otiv, uk sohk kuy xi cuz.
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)"
}
roajw cohux bank vpdoyq ab uz udd’v rol, za as’f ceoquwziir gu nimo uqrif wmi ebg op zqo pfuwaji. Neu vtatz i tuuvuwde yurbipo om gibm oy ley zruc biyu, ovb gyu plereuul yafvomv ep sazu.
Challenges
Before moving on, here are some challenges to test your knowledge of memory management. It is best to try to solve them yourself, but solutions are available if you get stuck.
Ldawo ginu cowh gti firypaew ic ego acuohipti ej tqo dbemkaz dioq’b xiujca zami qudn jotmop oh gde ukryozaszool.
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 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 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.