Keran Marinov
4 min readJan 12, 2018

Swift Unit Testing Memory Leaks

When working with large projects as the time goes on, you realise that more code you add the possibility of memory leaks increase.

Figuring out where the memory leaks are, may require using monitoring tools and it is not ideal most of the time, but sometimes it might be the only way to find out.

But what if there was an easier way to make sure every class you create does not leak memory, especially your view controller may have quite few dependencies.

I won’t go too much into details about creating strong reference, automatic reference counting etc…

I will simply explain, assigning nil to an instance should call deinit method in your class, if this is method is not called, it means your object is not deallocated.

import UIKitclass ViewController: UIViewController {deinit {    print("deinit called")  }}var viewController: ViewController! = ViewController()viewController = nil

Running this code in Playground you should see deinit called printed out, which means viewController is deallocated.

Now lets create some memory leak with RxSwift bindings and make our view controller to leak memory. Apparently the following binding causes deinit method not to be called.

I have not done a proper investigation on why it is the case. But I guess it is creating a strong reference under the hood.

The MainViewController below is very simple and has button tap binding to a method called leakingMethod.

import UIKitimport XCGLoggerimport RxSwiftimport RxCocoa
class MainViewController: UIViewController, DeinitalizeNotifiable { @IBOutlet weak var button: UIButton! var disposeBag: DisposeBag = DisposeBag() var mainViewModel: MainViewModel! var onDeinitialized: (() -> Void)? override func viewDidLoad() { super.viewDidLoad() setUp() } deinit { onDeinitialized?() XCGLogger.debug("deinit") } func setUp() { button.rx.tap .bind(onNext: leakingMethod) .disposed(by: disposeBag) } func leakingMethod() { XCGLogger.debug("leaking")

}
}

Now we want to prove that MainViewController is leaking memory but first we need to know when the deinit method is called. There is another alternative way to figure out this, if you google you will be directed to the Medium blog for the solution. However in my case it was not possible to use that approach. I wanted to test this class without subclassing. But how can we know if the deinit called without subclassing?

Let’s try the approach below, first create the following protocol called DeinitalizeNotifiable which will force the implementing classes to have an optional closure property.

Feel free to come up with a better name, mine might be too descriptive for some.

protocol DeinitalizeNotifiable {    var onDeinitialized: (() -> Void)? { get set }}

Add the DeinitalizeNotifiable protocol to MainViewController.

Compiler will complain and offer you to add the onDeinitialized property.

class MainViewController: UIViewController, DeinitalizeNotifiable {

var onDeinitialized: (() -> Void)?
.....
}

Next step in our implementation is, to make sure that the closure is being called from the deinit method, add the onDeinitialized?() in your deinit method like below.

deinit {   onDeinitialized?()   XCGLogger.debug("deinit")}

Ok, we are good to go, now we have created the MainViewController with memory leak, but since we have the safeguards we can figure out what is wrong.

Lets start with test implementation, I will use Quick and Nimble, but the same can be achieved with XCTest ( After few trials seems like XCTest version of this test might not work as expected please refer to this post https://github.com/borg666/ios-memory-leak-buster/issues/1 ) as well.

First we describe which class we are testing, in our case it is MainViewController, then in the context we describe what case is being tested. Inside the beforeEach we do the set up and find out the result of our test. Inside the it block we verify if our test pass or fail. You can find more about BDD Testing frameworks, on the web.

Going back to our test, I will try to explain step by step, what is going on, first we initalize our MainViewController with implicit type ! since we will set it to nil at some point during the test.

Now that we know when we set mainViewController instance to nil, if we don’t have any memory leak, deinit method should call the onDeinitialized closure, which we assign our closure with following syntax.

mainViewController.onDeinitialized = {    done()  // this stops the waitUntil    isDeinitCalled = true }

Finally, this is where we verify if our test pass or fail. In our case, this test will time out after 5 seconds, and fail.

it("deinit called") {   expect(isDeinitCalled).to(beTrue())}

With this approach you can make any class conform DeinitalizeNotifiable protocol and test for memory leak.

Here is the Github link for the full project that demonstrates test pass and fail cases.

On a side note, not using any gif in a blog is like not having singletons in your project.

Thanks for reading, and hope you find this useful.