Everything you need to know about Memory Leaks in iOS
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:
- What is memory Leaks in iOS
- Why Memory Leaks
- How ARC unable to free memory
- Memory Leaks in Closure
- Possible Solution
- Some Special Scenarios (Singleton and Static Classes Memory Leaks)
- Non-escaping closures
- Difference Between weak and unowned
- Identify Leaks Using Memory Graph Debugger
- Some Rule of Thumbs
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:
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:
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:
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:
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:
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:
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:
- 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.
- Second, we increment the User object Reference count which now has two strong references pointing to it which makes it reference count to 2.
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:
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:
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:
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:
user = nil
Setting user
to nil will decreases the reference count of User object and make it to 0:
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:
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 → selfClosures 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:
- First, we define two local variables
a
andb
with values 20 and 30 respectively. When we define any variable in a method it can only be accessed within the scope. - Second, we define
someClosure
variable that capturesa
andb
by strong reference to it to prevent those values from being released, and the closure from crashing in case they were deallocated. - When
someMethodThatTakeClosure
method call it will execute the closure and closure will return the sum ofa
andb
which were captured onviewDidLoad.
As shown in Figure 14 sum value that was printed 50.
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.
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.
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:
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:
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:
Now after popViewController following action will perform:
- The navigation controller removed the ViewController from stack and decrement the reference count to 0.
- ARC looks zero reference count instance and found ViewController instance then removed it from memory and also removed their associated ownerships.
- 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.
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:
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:
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 😵
As shown in Figure 23:
- The navigation controller holds a strong reference to SecondViewController makes it reference count to 1.
- Closure captures self strongly makes SecondViewController instance RF = 2.
- newSomeClosure variable holds a strong reference to a closure that makes RF = 1.
When you popviewController following things happen as shown in Gif 1:
- SecondViewController RF = 1
- 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
- Closure RF = 0 removed all ownership from him and make the SecondViewController RF = 0 which ultimately rehomed ViewController from memory
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)
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()}
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()}
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
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:
We found in ViewDidLoad as shown in Figure 28 and fix it in 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:
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:
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:
- https://medium.com/@stremsdoerfer/understanding-memory-leaks-in-closures-48207214cba
- http://angelolloqui.com/blog/21-ARC-I-Introduction-to-ARC-and-how-it-works-internally
- https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-ID51
- https://marcosantadev.com/capturing-values-swift-closures/
- https://stackoverflow.com/questions/51429037/potential-memory-leak-using-high-order-swift-functions
- https://stackoverflow.com/questions/43693703/swift-weak-self-in-didset