Abolish Retain Cycles in Swift with a Single Unit Test

Can't believe we're still dealing with this in 2017? Well, that makes two of us. While retain cycles are easy to fix, they're also hard to spot while eyeballing a codebase. Recently, I've found that a single unit test can provide solace. With just a few lines of code, it runs continuously as you make changes, all the while verifying you haven't introduced any new memory leaks.

It's a simple snippet, but I'll walk you through it.

The test function makes use of XCTest's asynchronous expectation system to pick up whether your class's deinit function gets called. The subclassing trick you see there is needed because Swift doesn't support reflection yet. The subclass tracks the deinit call by adding that deinitCalled closure.

The test works by allocating the instance on the main queue, while immediately deallocating it on the background queue. This triggers the deinit call, and the test succeeds.

If the test fails due to the 5-second timeout, this is your cue. You have just found a retain cycle! I've found it helps to run this test throughout the development process. The delta between code changes remains small enough to pick up where you forgot a [weak self] or [unowned self].

P.S. I was unable to figure out a way to make the test function generic, so I've been copy-pasting it around. I realize this is not ideal. However, I find it's not a big deal because we're dealing with test code, not actual app code.