Subscribe now

Memory Management for Classes [11.09.2016]

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!