Memory Management
Reference Counting as a Time Problem
You've read the chapter on Automatic Reference Counting. So you already know that every strong reference increments an object's reference count by 1, and that an object's memory is free'd once its reference count falls to 0.
We'll create a demo class that prints out its events to the debug area.
class SomeClass {
init() {
print("SomeClass init")
}
deinit {
print("SomeClass deinit")
}
}
Swift calls the
deinit
method after the reference count hits 0, and before the memory is freed. For many classes, you won't need to write a
deinit
, method, because stored properties are automatically released. We're going to use it to see exactly when objects get freed.
var storageForSomeInstance:SomeClass? storageForSomeInstance = SomeClass()
Here's a simple example, which places an object in a reference. In the debug area, we read:
SomeClass init
That means our init event was recorded. But there is no
deinit
. To get that, we would have to cause the reference count to hit zero by setting the reference to
nil
.
storageForSomeInstance = nil
And now in the debug area, we see both the
init
and
deinit
.
SomeClass init
SomeClass deinit
Let's see how this works when inside a function.
func doSomething() {
print("called doSomething()")
var someInstance:SomeClass? = SomeClass()
print("leave doSomething()")
}
doSomething()
In the debug area, we read:
called doSomething()
SomeClass init
leave doSomething()
SomeClass deinit
See the
SomeClass deinit
at the end, after
leave doSomething()
? That means when the scope of
doSomething
ended, the reference count of our object decremented to zero, and the instance was freed.
However, if that reference had been weak
func doSomething() {
print("called doSomething()")
weak var someInstance:SomeClass? = SomeClass()
print("leave doSomething()")
}
doSomething()
//called doSomething()
//SomeClass init
//SomeClass deinit
//leave doSomething()
The
SomeClass deinit
happens before the function leaves. A
weak
reference in Swift is "very weak". In fact, the object doesn't even survive to the next line:
func doSomething() {
print("called doSomething()")
weak var someInstance:SomeClass? = SomeClass()
print(someInstance)
print("leave doSomething()")
}
doSomething()
//called doSomething()
//SomeClass init
//SomeClass deinit
//nil
//leave doSomething()
We see the nil before
leave doSomething()
. That's because the weak reference does not increment the reference count. With 0 references, the SomeClass instance is immediately freed.
Automatic Reference Counting as a Graph Problem
So far, we've looked at reference counting as a time-based problem, when objects are freed. But the primary advantage of Automatic Reference Counting is that it transforms that into a graph problem.
So what is that problem, exactly? The one potential downfall of reference counting is the "retain cycle."
class SomeClass {
var anotherInstance:SomeClass?
deinit {
print("deinit")
}
}
var instanceA:SomeClass? = SomeClass()
var instanceB:SomeClass? = SomeClass()
instanceA.anotherInstance = instanceB
instanceB.anotherInstance = instanceA
instanceA = nil
instanceB = nil
Hmmm... No
deinit
in the debug area. Since each object is owned by the other, neither one will ever reach a 0 ref count, so neither will ever
deinit
. This is called a "retain cycle". It's how memory leaks. Garbage collection in other langugages, like JavaScript, would catch this. Garbage collection is constantly trying to solve this problem, meaning the end user literally pays for it in an ongoing way. ARC expects you, the developer, to solve this once, so your users don't have to.
We break retain cycles with the judicious use of
weak
references. In this case, I make the
anotherInstance
reference weak, and the deinit's happen
class SomeClass {
weak var anotherInstance:SomeClass?
deinit {
print("deinit")
}
}
var instanceA:SomeClass? = SomeClass()
var instanceB:SomeClass? = SomeClass()
instanceA.anotherInstance = instanceB
instanceB.anotherInstance = instanceA
instanceA = nil
instanceB = nil
//deinit
//deinit
So now the question is, how does the developer know which references should be strong and which should be weak?
Think of every object in your app as a tree of objects, with your application at the root.
class OwnerClass {
var myOwnedObject:OwnedClass?
}
class OwnedClass {
init() {
}
}
All of these "owning" or "parent" objects should hold strong references to objects they need to hang around to get their jobs done.
class OwnerClass {
var myOwnedObject:OwnedClass?
}
class OwnedClass {
weak var owner:OwnerClass?
init(owner:OwnerClass) {
self.owner = owner
}
func involvingParent() {
guard let parent = owner else { return }
//code involving temporary strong "parent"
}
}
The owned or child objects should never keep a strong reference to their parents (for longer than a function call), that would create retain cycles. Member references to parents should always be
weak
.
Next is what trips people up. Sibling references. Many developers accidentally use strong references for siblings. It is slightly less code, btw.
class Node {
weak var parent:Node?
var leftChild:Node?
var rightChild:Node?
}
This
Node
class
correctly strongly references its children, and weakly references its parent.
class Node {
weak var parent:Node?
var leftChild:Node?
var rightChild:Node?
var leftSibling:Node? //anti-pattern
var rightSibling:Node? //anti-pattern
}
But adding strong sibling references means we create retain cycles.
While theoretically any node could calculate its siblings by querying the parent. Sometimes our class simply won't have that much knowledge of its parent.
class Node {
weak var parent:Node?
var leftChild:Node?
var rightChild:Node?
weak var leftSibling:Node?
weak var rightSibling:Node?
}
Sibling references are properly weak. This adds a little extra code, because you'll need to write
guard let
in every function which references them, but this is the only way to write safe sibling references in Swift.
Disowning Unowned
There is another kind of reference promoted by the Swift ebook,
unowned
, but I’m going to show you why it lacks the kind of safety you’d expect in Swift.
An
unowned
reference is designed to point back to the owner of an object. It is weak in the sense that it does not change the retain count.
class OwnerClass {
var myOwnedObject:OwnedClass?
}
class OwnedClass {
unowned let owner:OwnerClass
init(owner:OwnerClass) {
self.owner = owner
}
}
In this case, the
OwnedClass
does not function properly without an
owner
object, for whatever reason. Since the
owner
reference is not
weak
, and is not an
Optional
, code involving it can be much simpler. It’s also only ever referenced strongly by an instance of
OwnerClass
. The idea is that
OwnedClass
’s
owner
reference will be valid as long as the object which owns it exists, and the
OwnedClass
instance can’t stay around for longer than the object which owns it.
The down side is the
owner
reference is not safe. If the
OwnerClass
object it points to gets
free
’d, and the reference is accessed, the app
crashes
. That’s a hefty penalty for making a simple mistake, like letting a reference go into a multi-threaded context which just happens to complete execution milliseconds after the user has decided to move to another portion of the app, destroying the
OwnerClass
instance, thereby making the owner reference invalid. See? That was an easy mistake to make. In addition, the compiler will not enforce that no one else can get a reference to the
OwnedClass
, and not enforce that all of its method complete execution before the owner is free’d. In stark contrast to Swift’s other flexible, powerful features,
unowned
stands out as a dangerous lurking corner, meant only to reduce optional operators and conditionals associated with safety dereferencing a
weak
reference. I highly recommend you do not use an
unowned
reference. Ever.
Summary
Today we looked at reference counting as both time and graph problems. We saw what could happen if we created a retain cycle. And we learned where to use weak references. Lastly, we disowned unowned references.
Automatic Reference Counting? Now that's Swift!
Comments for Memory Management for Classes