What the **** is a Memory Leak?!

What the **** is a Memory Leak?!

In any software environment, it's very common to talk about memory leaks, according to NetGuru, a memory leak is a block of memory that was allocated at some point in time but for some reason, was never released and is no longer being referenced in the app [1].

Before we dive into how to protect ourselves from memory leaks, we should learn about how they appear in the first place.

One of the features that makes Swift considered a modern language is due to an Automatic Reference Counter (ARC) for memory management [2].


ARC

According to Swift's official documentation [3]:

Swift uses Automatic Reference Counting (ARC) to track and manage your app’s memory usage. (...)
ARC automatically frees up the memory used by class instances when those instances are no longer needed.

When you create a new instance of a class, ARC allocates a block of memory to store the information of that instance. When this is no longer needed, ARC frees up the memory used by it. This ensures the instances don't take unnecessary memory when no longer needed.

A problem arises when ARC deallocates an instance that is still in use. And when trying to access the instance your app would most likely crash. So to avoid this ARC also keeps track of how many elements are currently referring to a given instance. ARC won't deallocate an instance as long as there is at least one active reference.

How ARC Works by Example

To follow along with the diagrams I'm going to start this section in the article with the legend, you can come back here to check what each element means.

So let's imagine the following scenario: We create a class of type Person that has 2 properties, a name (of type String) and an apartment (of type Apartment). Since not everyone owns an apartment we can set this later property as optional.

We also need to create a class called Apartment, Apartment has a name (of type String as well) and a tenant (of type Person). Since not every apartment has a tenant it can also start by being optional.

We can represent this with the following diagram:

So if I create an instance of type Person and an instance of type Apartment how many references will I have to Person and Apartment?

The two variables will have a strong reference each to the class they are instantiating.
So, 1 for Person and 1 for Apartment, as reflected in the next diagram.

If I set the tenant of the apartment as person1 and the apartment of person1 as apartment1, how many references will I have?
In that case, person1 has 1 reference to Person and 1 reference to Apartment and apartment1 has 1 reference to Apartment and 1 reference to Person.

If for some reason I don't need the person1 and apartment1 anymore and set them to nil, will the memory management be able to deallocate both instances?

No, since the reference counting for each element is still one even after the instance no longer exists.

This happens because both instances hold a strong reference to each other, causing this memory leak.

Using Weak References

One of the possible solutions to avoid memory retention is using weak references. According to Apple [4]:

A weak reference is a reference that doesn't keep a strong hold on the instance it refers to, and so it doesn't stop ARC from disposing of the referenced instance. This behaviour prevents the reference from becoming a part of a strong reference cycle.

Let's use the previous example, but now let's apply a weak reference to it. Now the apartment will have a weak reference to a tenant instead.

And we're going to do the same as before, let's reference the instances to each other.

Now, as you can see here since the tenant is of type weak, it's possible to deallocate the instance person1 while maintaining the weak reference. ARC automatically sets a weak reference to nil.

Since there are no more references to the Person instance it can be deallocated.

Now, we still have a strong reference to the apartment on the person1 instance, but if this apartment1 ceases to exist the counter to the instance apartment1 drops to 0 and can also be deallocated.

Unowned References

Even though most of the prevention of memory leaks can be done using weak referencing it's important to mention unowned references. They also don't keep a strong hold on the instance but they can be used when the other instance has the same or a longer lifetime [5].

How to Prevent Memory Leaks Using Unit Tests

After this whole explanation, we can now move forward to how we can prevent memory leaks using unit tests.

When implementing unit tests, it's usual to use the Factory Method Pattern to encapsulate the creation of the objects. My suggestion is to check there if the instances you are creating are nil when the test finishes by adding this in an addTeardownBlock[6].

    func trackForMemoryLeaks(
        _ instance: AnyObject,
        file: StaticString = #file,
        line: UInt = #line
    ) {
        addTeardownBlock {[weak instance] in
            XCTAssertNil(instance, "Instance should have been deallocated. Potential memory leak.")
        }
    }
}

You can make this method an extension of the XCTestCase class and use it in all of the factory methods of your unit tests.

Big thank you to the Essential Developer's course by Caio & Mike it was on this course that I learned this powerful tool to add on to your unit tests.

Thank you for taking the time to read this article.

If you enjoyed my work and want to stay updated on future projects, don't hesitate to connect with me on GitHub or LinkedIn. Thank you 🙏🏻

References

[1]https://www.netguru.com/blog/what-are-ios-memory-leaks-and-how-to-detect-them

[2]: Develop in Swift Fundamentals: Getting Started with App Development page 17

[3]: Automatic Reference Counting in Swift https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting/#Resolving-Strong-Reference-Cycles-Between-Class-Instances

[4]: Weak References: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting/#Weak-References

[5]: Unowned References: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting/#Unowned-References

[6]: addTeardownBlock: https://developer.apple.com/documentation/xctest/xctestcase/2887226-addteardownblock