Everything you need to know about Memory Leaks in iOS

Ali Akhtar
16 min readApr 27, 2019

--

Memory Graph Debugger

If you know ARC and what is memory leaks in iOS you can start from section 2. This is a very long article in this article we will look:

  1. What is memory Leaks in iOS
  2. Why Memory Leaks
  3. How ARC unable to free memory
  4. Memory Leaks in Closure
  5. Possible Solution
  6. Some Special Scenarios (Singleton and Static Classes Memory Leaks)
  7. Non-escaping closures
  8. Difference Between weak and unowned
  9. Identify Leaks Using Memory Graph Debugger
  10. Some Rule of Thumbs

Swift uses Automatic Reference Counting (ARC) to track and manage your app’s memory usage. In most cases, this means that memory management “just works” in Swift, and you do not need to think about memory management yourself. ARC automatically frees up the memory used by class instances when those instances are no longer needed.

Section 1

Memory Leaks in iOS

A memory leak occurs when a given memory space cannot be recovered by the ARC (Automatic Reference Count)because it is unable to tell if this memory space is actually in use or not. One of the most common problems that generate memory leaks in iOS is retained cycles we will see it later.

How ARC is unable to Free memory?

Every time you create a new instance of a class, ARC allocates a chunk of memory to store information about that instance. When an instance is no longer needed it frees up the memory so the question is how ARC identifies the instance is no longer needed. To understand it we first need to look at how ARC internally works

Every variable is declared as strong by default. When you create an instance of a class with strong it increments the reference count of that instance. The reference count is the number of strong references to that instance. When ARC sees any variable that has Reference count to 0 it frees up the memory of that instance memory:

var referenceOne: Person? = Person()

As shown above we create an instance of Person object. ARC will allocate memory and store reference to referenceOne variable since that variable holds a strong reference to Person object instance ARC will increment the reference count to 1 as shown in Figure 1:

Figure 1

var reference2 = referenceOne

After executing the above statement we create another strong reference to Person object instance by assigning its address to reference2 variable. As shown in Figure 2 now reference count = 2 after assigning another strong reference to the Person object:

Figure 2

referenceOne = nil

After executing the above statement we removed a strong reference to Person object by assigning nil value to referenceOne variable to break the strong reference. As shown in Figure 3 now reference count = 1 after removing strong reference to the Person object:

Figure 3

reference2 = nil

After executing the above statement we removed a strong reference to Person object by assigning nil value to reference2 variable to break the strong reference. As shown in Figure 4 now reference count = 0 after removing strong reference to the Person object:

Figure 4

When Reference Count = 0 ARC will remove the Person object from memory. This is how ARC works.

Still question How memory will leak

As we understood how ARC works if it sees any variable with Reference Count = 0 it free the space what if Reference Count never goes to 0 it’s possible to write code in which an instance of a class never gets to a point where it has zero strong references. So let's take an example and reproduce this case.

Question: When ARC Try To See Variable RC (Reference Count)

When you are working with cocoa or cocoa touch application is simply to assume that your run loop is gonna run any time you give up control of the thread of execution in your application, so any time you exit a method or there are certain methods you can call on NSObject that will run the current run loop (for loop) , anytime you do anything like that you have to assume that autorelease pool is going to be drained. At each drained they check if variable has RC = 0 it dealloc it. In Short At the end of every run loop it check this

Rule of thumb : If no one is owning an object ARC will free up the memory OR
If no one has strong reference to an instance of an object ARC will free up the memory OR
If an object Reference Count = 0 ARC will free up the memory OR
If an object has zero strong reference, the object will free from the memory

Here’s an example as of how a strong reference cycle (means Reference count never reaches zero)can be created by accident. As shown in Figure 5 we define two classes called User and Todo. User has todo property which stores the strong reference to todo object as we said earlier by default property created with a strong pointer to an object:

Figure 5

var user: User? = User() //reference count 1 User object

var todo: Todo? = Todo() //Reference count 1 Todo Object

By executing the above statements we created an instance of User and Todo object. ARC will allocate memory and store strong reference of the User object to user variable since that variable holds a strong reference to User object instance ARC will increment the reference count to 1 same goes to todo object. As shown in Figure 1 user variable holds a strong reference of User object whereas todo object is holding Todo object instance:

Figure 6

user?.todo = todo //reference count 2 Todo Object

todo?.associatedUser = user //reference count 2 User Object

By executing the above statements we performed the following actions as shown in Figure 7:

  1. First, we increment the Todo object Reference count which now has two strong references pointing to it which makes it reference count to 2 which also means Todo objects have two owners.
  2. Second, we increment the User object Reference count which now has two strong references pointing to it which makes it reference count to 2.
Figure 7

user = nil //reference count 1 User object
todo = nil //reference count 1 Todo object

Ideally, when you set Object to nil it’s deinitializer should be called. In these scenarios, neither deinitializer was called when you set these two variables to nil The strong references between the User instance and the Todo instance remain and cannot be broken and there is no way to free these objects' memory. This is called Strong Reference cycle:

Figure 8

Solution

There are two solutions to this problem make one of the property to weak or unowned. Weak and unowned references enable one instance in a reference cycle to refer to the other instance without keeping a stronghold on it. The instances can then refer to each other without creating a strong reference cycle. As compared to strong weak does not increment reference count.

As shown in Figure 9 we created weak property name associatedUser in Todo class which means assigning User object to this property will not increment it’s reference count / will not hold a strong reference to User object:

Figure 9

var user: User? = User() //reference count 1 User object

var todo: Todo? = Todo() //reference count 1 Todo Object

By executing above statements we created User and Todo objects in user and todo variable holding a strong reference to these objects respectively which makes both objects reference count to 1:

Figure 10

user?.todo = todo //reference count 2 Todo Object

todo?.associatedUser = user //reference count 1 User object

Here’s how the references look now that you’ve linked the two instances together as shown in Figure 11 User has strong reference to TodoObject which make it Reference Count to 2 whereas Todo object has weak reference to User object which doesn’t change its reference count:

Figure 11

user = nil

Setting user to nil will decreases the reference count of User object and make it to 0:

Figure 12

Now ARC while checking all objects Reference count found User object reference count 0, it frees up the memory and associated reference ownerships/strong reference as shown in Figure 13. Todo object with associatedUser property set to nil since it has a weak reference. User object is now free it’s all strong reference holding property will also nil which decreases the reference count of Todo object as well resulting it to 1:

Figure 13

todo = nil

Setting todo to nil will make it reference count to 0 and ARC will free Todo object from memory. We solved Strong reference cycle using weak reference which will create our rule number 1.

Rule 1 :

When two objects has a bidirectional relationship make one of them weak or unowned.

Memory Leaks in Closure

Rule of Thumb
Memory Leak in Closure = self refers to → object refers to → self

Closures are self-contained blocks of functionality that can be passed around and used in your code. Closure captures variables and constants from its surrounding scope.

Capturing ?

As said above Closure captures variables and constants from its surrounding scope. Let’s see in action. We performed a number of steps in Figure 14:

  1. First, we define two local variables a and b with values 20 and 30 respectively. When we define any variable in a method it can only be accessed within the scope.
  2. Second, we define someClosure variable that captures a and b by strong reference to it to prevent those values from being released, and the closure from crashing in case they were deallocated.
  3. When someMethodThatTakeClosure method call it will execute the closure and closure will return the sum of a and b which were captured on viewDidLoad. As shown in Figure 14 sum value that was printed 50.
Figure 14

Section 2

Memory Leaks in Closure

When we create a closure the value that it needs for its execution it holds a strong reference to it what if the value needed by the closure is self and closure is a property of the controller.

As shown in Figure 15 we created an optional closure as a property of the controller with type () -> Int and assign value to it in viewDidLoad. Closure needs a and b properties to execute it. Also since a and b are the properties of a class it will have to capture class reference as well by capturing self. These are the only external values it needs, and to keep those values available.

Figure 15

var someClosure: (() -> Int)?

First, we are saying that self.someClosure store a strong reference to the closure:

someClosure = { return self.a + self.b }

Since ViewController and Closure both are reference type ARC reference counting system will apply on both.

By executing the above statement we define closure and assign it to the class property which holds a strong reference to closure and increment the reference count of closure to 1 as shown in Figure 16.

Since Controller was pushed on the navigation controller. Navigation controller create a strong reference to ViewController which increment the Reference count to 1 of the controller which also prevents ViewController from dealloc.

At the same time since closure needs a and b property it holds/captures the class reference as well which makes the reference count of the class by 2.

Figure 16

When you pop ViewController it removed the controller from the navigation stack which decreases its reference count to 1.

Ideally, ViewController deinit should call but we created a retain cycle. This occurs when we make circular references between two or more objects as shown in Figure 17.

ARC looks and not found any reference count 0 so it will not remove both from memory:

Figure 17

Solution

Make one of the ones references as weak/unowned and the other as strong, therefore, the circular reference is broken.

As shown in Figure 18 closure now capturing a weak reference to self. since we are using weak which makes self as optional that’s why we use a guard to safe unwrap the value of self:

Figure 18
var someClosure: (() -> Int)?
self.someClosure = { [weak self] in guard let `self` = self else { return 0 return self.a + self.b}

As shown in Figure 19 Navigation controller create a strong reference to SecondViewController which makes it reference count to 1 and SecondViewController is owning closure since closure is a class property which makes the reference count of closure to 1. But now closure weakly capture self as we defined in capture list which will not increment the reference count of closure as shown below:

Figure 19

Now after popViewController following action will perform:

  1. The navigation controller removed the ViewController from stack and decrement the reference count to 0.
  2. ARC looks zero reference count instance and found ViewController instance then removed it from memory and also removed their associated ownerships.
  3. Since ViewController strong and weak associated reference also removed which decrement the reference count of closure to 0 and ultimately removed it from memory.

Difference Between weak and unowned:

Unowned

Like a weak reference, an unowned reference does not keep a stronghold on the instance it refers to (means it can use to break the retain cycle).

Unlike a weak reference, however, an unowned reference is used when the other instance has the same lifetime or a longer lifetime.

Let’s put in our case which means if you are sure that when closure executes self is available otherwise the application will crash. OR self has a longer lifetime then closure.

Download the starter project

Run application >> it will display screen having button >> Tap on Button it will Push SecondViewController >>

On ViewDidLoad It executes someMethodThatTakeClosure which takes 4 seconds. After 4 seconds it will execute closure which requires self so before 4 seconds if we pop secondviewController application will crash as shown in Figure 20 since in this case closure have a longer lifetime and Second ViewController also deallocated:

Figure 20

Fix

Use weak self as shown in Figure 21 which makes self optional and using guard we can safely unwrap self. If the self is already deallocated we can handle properly:

Figure 21

Rule of Thumb

Use weak if you are not sure your capture self in a closure may or may not available in some cases.

Use unowned if you are 100% sure that self will be available when you're closure executes.

Rule 2:

When you have class level closure and inside closure, if you are accessing anything using self make self weak /unowned.

No Memory Leak Case 1

Local method closure capturing self will not create any memory leaks

As shown in Figure 22 we created closure in a local scope of someMethod since closure is not a class property of a controller, the controller doesn't hold a strong reference to closure but closure captures self strongly as shown in Figure 22 which means when closure execute controller will deallocate. In this case, closure always has a longer lifetime 😵

Figure 22

As shown in Figure 23:

  1. The navigation controller holds a strong reference to SecondViewController makes it reference count to 1.
  2. Closure captures self strongly makes SecondViewController instance RF = 2.
  3. newSomeClosure variable holds a strong reference to a closure that makes RF = 1.
Figure 23

When you popviewController following things happen as shown in Gif 1:

  1. SecondViewController RF = 1
  2. When the closure executes and local scope which holding it strongly removed from the stack. Closure RF will decrease by 1 which make it 0
  3. Closure RF = 0 removed all ownership from him and make the SecondViewController RF = 0 which ultimately rehomed ViewController from memory
Gif 1

No Memory Leak Case 2

The static method that captures the self does not create any memory leaks.

As shown in Figure 24 self is not owning static class but static class closure capturing self strongly.

Static Class → Closure → Self (Will not create memory leaks)

Figure 24

The code below will not leak any memory since Class not owning DispatchQueue.

DispatchQueue.main.asyncAfter(deadline: .now() + 2) {self.execute()}

No Memory Leak Case 3 non-escaping closures

Any Class owing reference to self within a closure should be a reference to unowned self or weak self to prevent a reference cycle. Note that non-escaping closures don’t require a reference to self:

A non-escape closure tells the complier that the closure you pass in will be executed within the body of that function so do not need to use weak self.

Memory Leak

Rule of Thumb:

If you are accessing Singleton / Static class that holds closure in their class The time in which it holds the closure memory will leaks in this time duration.

As shown in Figure 25 we have a singleton class that holds closure in someSingletonMethod and after executed closure, that method scope will end and closure will free from memory since local variable holding the closure strongly is removed:

SingletonClass.shared.someSingletonMethod(self.a) { (value) inself.execute()}
Figure 25

As shown in Figure 26 we have a singleton class that holds closure in someSingletonMemoryLeakMethod and when executing it holds a strong reference of that closure in class level property and after executed closure, it will not release it since Singleton class always remain in memory it will hold the closure which eventually created memory leak. In this case, a memory leak will occur.

Memory Leak = SingletonClass → Closure → Self

Until SingletonClass remain in Memory, self (ViewController) will remain in memory.

When we do pop controller will not deallocate since closure still holding it strongly which is stored or not released by Singleton Class:

SingletonClass.shared.someSingletonMemoryLeakMethod(self.a) { (value) inself.execute()}
Figure 26

Solution

Use a weak self to avoid memory leak:

No Memory Leak = SingletonClass → Closure

Now SingletonClass only holds closure reference since closure is using weak self When we do pop controller it will deallocate since it has only navigation controller owner.

This scenario same goes to static class:

Don't Care Memory Leak when the closure is Non-Escaping

Higher Order function Leak Memory

High order function uses a strong self leading to possible memory leaks

array.filter { [weak self] item in return self?.byTime(item) }

Property Observer

didSet is not a closure, you cannot use a closure syntax for it.

There is no reason to use weak self there. a didSet handler won’t create ownership cycles in the same way a method doesn’t create them.

It’s nonsensical to use [weak self] because didSet does not capture anything and will never create retain cycles.

Identify Memory Leaks Using Memory Graph Debugger

we know the fix in this part we will only identify memory leaks and fix by using previous techniques. The main purpose is to use Memory Graph Debugger to identify leaks in an existing project. Download the starter project.

Memory Graph Debugger

In one sentence, the memory graph debugger helps to answer the following question: Why does an object exist in memory?

The Xcode memory graph debugger helps find and fix retain cycles and leaked memory. When activated, it pauses app execution, and displays objects currently on the heap, along with their relationships and what references are keeping them alive.

Open starter project and run application tap on Button will redirect to a new screen. Tap until you reach the screen having title “Third View Controller Title”, Now tap back a to pop Third View Controller will display “Second View Controller”. After tapping back you need to see whether the memory is leaking in Third View Controller or not.

As shown in Figure 27 we are now in SecondViewController but our ThirdViewController still in memory which means it was not deallocated. By tap on this, we identify there is some closure holding it strongly:

Figure 27

We found in ViewDidLoad as shown in Figure 28 and fix it in Figure 29:

Figure 28
Figure 29

Now again run the application and follow the same step there will be no memory leaks. As shown there is no ThirdViewController in the heap which means it was deallocated:

Figure 30

We are happy that 😃 we removed all the memory leaks but still, there are some memory leaks when some events occur in this controller.

Run application again go to the ThirdViewController and tap on Button 1. On tapping this will create memory leaks since inside its action as shown in Figure 31 memory leak is happening:

Figure 31

In short:

To find memory leaks you need to perform every flows that your controller will perform and check in-memory graph debugger heaps or print something in deinit.

Useful Links:

--

--