Articles, podcasts and news about Swift development, by John Sundell.

Memory Management

Published on 28 Mar 2019

Just like modern versions of Objective-C, Swift uses the ARC (Automatic Reference Counting) memory management model. The core concept of ARC is actually quite simple — an object is retained in memory by incrementing its reference count, and then released by decrementing that same count. Once an object’s retain count reaches zero — that object is deallocated from memory.

The good news is that, in Swift, we don’t need to manually increment and decrement those reference counts — it’s all done for us under the hood, thanks to ARC. So for example, if we’re working on a logistics app and we have a Ship class that can contain Cargo — once we assign a cargo object to a ship, that cargo’s reference count is now one, since the ship is the only other object retaining it:

class Ship {
    var cargo: Cargo?
}

let ship = Ship()
ship.cargo = Cargo()

Our ship’s reference count is actually also one, not because any other object retains it, but because it’s being retained by our local ship variable. But since we’re not storing our ship anywhere, once the above code has finished executing, here’s what will happen:

Note that the above details might vary depending on whether the app was compiled for debug or release, and there are also other ARC implementation details that can effect the actual retain count — but in general, that’s how ARC operates from a high-level perspective.

The process outlined above may seem straight forward, but can cause some tricky situations if we’re not careful. One example is retain cycles, which occur when two objects reference each other, making it impossible for either to ever get deallocated — because both of their retain counts will always be one or greater.

For example, above we made Ship hold a reference to its Cargo — so if we also made Cargo reference its ship, then we’d have a retain cycle on our hands:

class Cargo {
    var ship: Ship?
}

let ship = Ship()
let cargo = Cargo()

// Now, neither our ship or cargo will ever be deallocated,
// since their retain counts will never reach zero.
ship.cargo = cargo
cargo.ship = ship

One solution to the above problem is to break the retain cycle, by making one of the references weak. A weak reference doesn’t increment the referenced object’s retain count, while still letting us get access to the object while it’s still in memory — which is really useful in situations like the above.

Since it makes more sense for the ship to own its cargo, rather than the other way around — let’s turn the Ship reference in our Cargo class into a weak var:

class Cargo {
    weak var ship: Ship?
}

And our retain cycle is broken! 🎉

Another common source of memory related issues is closures. Just like how referencing an object using a property increments its retain count, so does capturing it in a closure as well. So for example, if we’re using a closure to observe whenever a ship unloaded its cargo — and then also using that same Ship object within that closure — we’ll again cause a retain cycle:

ship.unloadObservation = {
    // Since we're using 'ship' here, it gets captured strongly
    // by the closure, causing a retain cycle, since the closure
    // is in turn owned by the ship itself.
    ship.cargo = loadNextCargo()
}

Breaking the above retain cycle also involves introducing a weak reference — this time to capture the Ship weakly instead of strongly (which is the default), by adding an explicit capture list to our closure:

ship.unloadObservation = { [weak ship] in
    // Since the ship is now captured weakly, it becomes an
    // optional within the closure's scope.
    ship?.cargo = loadNextCargo()
}

The important thing when setting up hierarchies of objects and closures is to carefully consider which type that will act as the owner for each given object. With a clear hierarchy, where parent objects are responsible for retaining their children (and children only ever hold weak references to their parents), a lot of memory related issues — such as retain cycles — can be avoided.

Thanks for reading! 🚀