This is the first article is a series of three articles regarding memory management in Swift. If you’re already versed in how Swift manages memory, you might be interested in skipping to the next article in the series.

If you’ve been an iOS developer for a while, in either Objective-C, or Swift, you’ve probably stumbled onto what is called a retain-cycle, whether it was a trivial one or a harder one to fix.

Having recently finished developing a heavily multithreaded iOS application, I thought I’d share my experience finding, debugging and fixing these dreaded things that are retain cycles.

This article (as well as the others in the series) will focus on Swift 4.1, but everything applies to other versions of Swift as well as Objective-C as long as you’re using ARC (if not, you’re on your own). I’ll assume you have some knowledge of the language, mostly on object-oriented programming, closures, optionals and properties.

Memory management basics

Memory management is based on reference-counting. Each object defined in Swift has a implicit variable called a reference count. This reference count is increased whenever an object is referenced somewhere, and so decreased when the object is not referenced anymore.

For example, given two objects Car & Driver, the reference count is increased whenever Car is reference by Driver, in which case Car’s reference count will increase by one. In that case, Car has no idea that Driver exists — so ’s reference count is left unchanged.

Once this reference count hits 0, the object is deallocated.

Here’s some code that illustrates this example:

class Driver {
    var car: Car
    init(_ car: Car) {
        self.car = car
    }
}

class Car {
}

// later
var myAwesomeCar = Car()
var me = Driver(myAwesomeCar)
// car is referenced twice: once in myAwesomeCar, and once in me.car
// me has only one reference

Now, you may be wondering why, since the memory seems to be managed automatically, is memory management a thing?

Let’s understand why!

The kind of reference we just saw is called a strong reference: for as long as Driver lives, Car will live as well (We can also say that Driver owns Car — which makes sense). Let’s look at another example now, in a world where the Car would need to know about the Driver:

final class ChatRoom {
    private var updatePeopleTimer: Timer!

    init() {
        class WeakTarget: NSObject {
            weak var chatRoom: ChatRoom?

            @objc func updatePeople(timer: Timer) {
                self.chatRoom?.updatePeople()
            }
        }
        let weakTarget = WeakTarget()
        weakTarget.chatRoom = self
        self.updatePeopleTimer = Timer.scheduledTimer(timeInterval: 5.0, target: weakTarget, selector: #selector(WeakTarget.updatePeople(timer:)), userInfo: nil, repeats: true)
    }

    deinit {
        self.updatePeopleTimer.invalidate()
    }

    private func updatePeople(timer: Timer) {
        print("Updating people")
    }
}

What could be the issue here? Now that Driver references Car (owns Car), and Car references Driver (owns Driver, sounds weird right?), we get what is called a retain cycle.

  • Driver has a strong reference to Car, so Car will live until Driver is deallocated.

  • But Car has a strong reference to Driver, so Driver will live until Car is deallocated.

  • But Driver has a strong reference to Car…

  • etc…

Hence the cycle. Here, breaking the cycle is simple: just set myAwesomeCar.driver to nil . You can try this online here, and uncomment line 27 to break the retain cycle.

But, what if Car wanted to just be able to access Driver, but not increase its reference count? In other words, what if Car just want to have a reference to Driver, and allow Driver to be deallocated by other classes?

That’s where another kind of reference can be used: the weak reference. In practice, if B declares its reference to A as weak, then it doesn’t own A anymore: B will have a reference to A for as long as A exists, and will be set to nil once it is deallocated. Here’s another example:

class Driver {
    var car: Car

    init(_ car: Car) {
        self.car = car
    }
}

class Car {
    weak var driver: Driver?
}

// later
var myAwesomeCar = Car()
var me = Driver(myAwesomeCar)
myAwesomeCar.driver = me
// myAwesomeCar is referenced twice: once in myAwesomeCar, and once in me.car
// driver is still referenced twice: once in me, and once in myAwesomeCar.driver
// However, this time, the reference in Car is weak: this means that once
// me is deallocated, myAwesomeCar.driver will be automatically set to nil.

If you run the same example as above, but with a weak reference, you’ll see that both objects are being deallocated. You can try it here.

Closures and reference counting

Whenever you define a closure in Swift, and you use instances of classes in it, they will implicitly be using a strong reference to the original instance. For example:

class MyViewController: UIViewController {
    private var action: (() -> Void)!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.action = {
            self.updateBackgroundColor()
        }
    }

    private func updateBackgroundColor() {
        self.view.backgroundColor = UIColor(
            red: CGFloat(arc4random()) / CGFloat(UInt32.max),
            green: CGFloat(arc4random()) / CGFloat(UInt32.max),
            blue: CGFloat(arc4random()) / CGFloat(UInt32.max),
            alpha: 1.0
        )
    }
}

Here, action is being retained by MyViewController, however the closure will also retain self: this creates a retain cycle (self > action> self > etc…). In order to circumvent this issue, Swift allows to specify a capture list, that tells the compiler how the objects referenced in the closure should be captured:

  • With a strong reference: the default

  • With a weak reference: weak

  • With an implicitely unwrapped reference: unowned

Given the code above, we know that action will be deallocated as soon as self is deallocated (action will never outlive self), it is safe to use unowned and not bother with explicitely unwrapped a weak reference. The code would become:

class MyViewController: UIViewController {
    private var action: (() -> Void)!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.action = { [unowned self] in
            self.updateBackgroundColor()
        }
    }

    // <...>
}

It would be an error to use unowned if action was not private, as it could very well be retained by some other object MyViewController has no knowledge of. If that’s the case, weak would be best:

class MyViewController: UIViewController {
    var action: (() -> Void)!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.action = { [weak self] in
            self?.updateBackgroundColor()
        }
    }

    // <...>
}

Some people advise using weak at all times, as it has the advantage of never crashing if a reference is used when it shouldn’t be — but in my opinion this could hide issues in code, and I prefer to use unowned in cases like the above.

A capture list also allows you to define variables that will be used in the closure. So if you wanted to reference the view controller’s view without having to reference self (thus avoiding the retain cycle), you could do:

class ViewController: UIViewController {
    private var action: (() -> Void)?

    override func viewDidLoad() {
        super.viewDidLoad()

        self.action = { [selfView = self.view] in
            selfView?.alpha = 0.5
        }
    }
}

Note that the unwrap operator ? is required here as the implicitely unwrapped optional status of self.view is implicitely changed to an optional when it gets assigned to selfView, as you can’t specify types in a capture list.

Since specifying self is optional in this context, you may also directly write:

class ViewController: UIViewController {
    private var action: (() -> Void)?

    override func viewDidLoad() {
        super.viewDidLoad()

        self.action = { [view] in
            view?.alpha = 0.5
        }
    }
}

This code has exactly the same behavior as the above.

That concludes this part on how closures interact with classes and reference-counting in general. The next article will show some examples of real-world issues, from more common to least common.

Related articles: