XCTest: The bane of memory leaks?

Mutual Mobile
Mutual Mobile
5 min readDec 19, 2022

--

A software developer, web developer, and mobile app developer enter a bar, and immediately turn around and run for their lives.

Why?

Short answer — memory leak

The long answer? Start scrolling to find out 😆

Before moving on, you might be wondering what is memory leak. It is a scenario that occurs when developers or software unintentionally do not free an allocated block of memory when it’s no longer required. In such situations, the performance of the software or application starts decreasing until it fails or becomes extremely slow.

So, how can this situation be resolved or prevented in the context of app development in Apple? Let’s take a look.

An apple a day keeps memory leaks away?

So, how does app development take place in the context of Apple’s app development ecosystem, i.e., iOS, macOS, watchOS, and tvOS?

Across Apple’s ecosystem, app development memory is managed with ARC’s (Automatic Reference Counting) help. ARC helps operating systems track, allocate, and deallocate memory for objects participating in a particular program. However, the OS does require developer’s assistance when it comes to managing the memory footprint.

The OS knows when to allocate and deallocate memory, but it doesn’t understand the reasoning behind managing the memory. Hence, it falls to the developer to design the interactions of objects in an application to ensure the ARC understands why memory has to be allocated and deallocated.

ARC manages the memory for an object by injecting retain and release method calls before and after its usage. The ARC keeps an object in memory as long as the object has a reference count of 1 or more. When the reference count falls to 0, the ARC will remove the object from memory. But what happens if the object is not removed from memory? Let’s examine it with an example.

As you can see in the graph above, several objects hold references to each other. Once ‘App Delegate’ gets deallocated, it removes the reference to ‘View Model’ while deallocating it as well. This similar process also continues with the ‘View Model’ and ‘Department Model’.

However, as you can see, ‘Department Model’ and ‘Employee Model’ still reference each other, even when no other object is referencing ‘Department Model’. Since they are still referencing each other, they exist in memory, thus causing the infamous memory leak (and giving you the long answer 😉)

How to catch memory leaks?

Apart from employing ARCs, there are other methods for catching or preventing memory leaks; tools and tests are two such ideas that come to mind.

Xcode has several tools that help developers debug and catch memory leaks; however, these primarily come into play after the features and functionalities are developed and can be time-consuming.

On the other hand, there are tests that can actually catch memory leaks, one of them being the XCTest. So, let’s examine how implementing an XCTest can help detect memory leaks, and here we dive into the practical world of coding.

Step 1

The first step in this process is creating an Xcode project and a test target. For this example, we’ll be creating four classes — Employee, Department, DepartmentViewModel, and Capture_Memory_Leak_with_XCTestTests, which is an XCTestCase subclass.

Step 2

Implementing the application’s main target is the next priority

import Foundation

class DepartmentViewModel {

var department: Department

init(_ department: Department) {
self.department = department
}

func add(_ employee: Employee) {
department.employees.append(employee)
}
}

class Employee {
var id: Int
var name: String
var department: Department

init(_ id: Int, name: String, department: Department) {
self.id = id
self.name = name
self.department = department
}
}

class Department {
var id: Int
var name: String
var employees = [Employee]()

init(_ id: Int, name: String) {
self.id = id
self.name = name
}
}

Step 3

The next step is to implement the application’s test target.

import XCTest
@testable import Capture_Memory_Leak_with_XCTest

class Capture_Memory_Leak_with_XCTestTests: XCTestCase {

/// holds references of instance of each type
/// On deallocation of `viewModel.department` these types will be nil'ed out.
private weak var weakDepartment: Department?
private weak var weakEmployee: Employee?

override func tearDown() {
super.tearDown()
assertMemoryLeakOnEmployee()
assertMemoryLeakOnDepartment()
}

private func assertMemoryLeakOnEmployee() {
/// Once `employee` instance is deallocated then `weakEmployee` will also gets deallocated & below test will success
XCTAssertNil(weakEmployee, "Employee Object is leaking")
}
private func assertMemoryLeakOnDepartment() {
/// Once `department` instance is deallocated then `weakDepartment` will also gets deallocated & below test will success
XCTAssertNil(weakDepartment, "Department Object is leaking")
}

func test_employeeExistsInDepartment() {
let iOSDepartment = Department(1, name: "iOS")
let abc = Employee(1, name: "ABC", department: iOSDepartment)

let viewModel = DepartmentViewModel(iOSDepartment)
viewModel.add(abc)

/// Entire memory leak magic happens here.
/// By end of this function, If proper object deallocation happens, it is expected that below instance(s) will be nil'lyfied
weakEmployee = abc
weakDepartment = iOSDepartment

let employeeExist = iOSDepartment.employees.contains {
abc.id == $0.id
}

XCTAssertTrue(employeeExist)
}
}

As you can see in the source code in steps 2 and 3, the weakEmployee and the weakDepartment instances in the application’s test target must be weak to ensure the test is carried out seamlessly.

So how do you find out or catch the memory leak? First, the presence of XCTAssertNil in the application’s main target informs us about the existence of the memory leak.

However, the leak occurs in the weakEmployee and weakDepartment instances from func test_employeeExistsInDepartment(). This function holds the local instance property in the test class scope, so during tearDown() method call asserting the deallocation of instances must be eliminated.

You can also handle the memory leak by prefixing weak or unowned to property var department: Department from the Employee class.

This is how you can prevent and catch memory leaks with XCTest, which would also help in attracting the software, web, and mobile developers who ran for their lives 😆

--

--

Mutual Mobile
Mutual Mobile

We’re an innovation consultancy that brings digital experiences to life through an integrated approach to design and technology.