Mallow's Blog

How to debug memory issues using Allocations in Swift

In this blog we gonna see about Debugging Memory issue in Swift. Before we deep dive into the concept we should learn some basic things.

Value Types Vs Reference Types

Value Types: Each instance keep a unique copy of its own data. Usually in Swift struct, enum or tuple falls into value types.

Reference Types: Many instance share a single copy of the data. In swift we have class which is reference type.

The above is a raw definition of value and reference types. The concept here is value types are stored in stack memory whereas reference types are stored in heap memory. Stack uses static memory allocation on the other hand heap uses dynamic memory allocation. Stack which is faster than heap usually get deallocated when it goes out of the scope (i.e normally out of the function or a class). But heap won’t deallocates automatically which is responsible for memory leaks. 

In swift we have a clean technology called Automatic Reference Counting which will deallocates the reference types automatically on our behalf. But we need to consider relationships between each instance we create.

ARC Mechanism

ARC works with simple mechanism, it will keep track of each reference type instance we create. If we create a reference for any instance, ARC will increment the reference count for that instance as 1. It tracks that instance and increments its reference count for each references we create.

But If we assign weak or unowned to the reference of any instance we create ARC won’t increment reference count for that instance. 

When a reference count for a instance becomes zero, ARC will deallocate the instance, if the instance is no longer needed.
Now we will see a simple example for retain cycle and will debug to break the retain cycle.

Project Details

Here we are having a sample project with two View Controllers FirstFloor and SecondFloor. In FirstFloor we are having a button to go SecondFloor and a label to display a message that we pass from SecondFloor.

In SecondFloor we are having a text view to type message and a button to send the message to FirstFloor.

Here we are having a delegate method in SecondFloor to pass message to FirstFloor.

protocol SecondFloorDelegate {
   func shareMessageToFirstFloor(message: String)
}

When a button is pressed in FirstFloor we will navigate to SecondFloor. Here you could see for the FirstFloor instance, we have created a reference and assigned to SecondFloor’s delegate method. So, reference count for FirstFloor is 1.

@IBAction func goToSecondFloor(_ sender: UIButton) {
   let reference = self
   secondFloorVC = UIStoryboard(name: "Main", bundle: nibBundle).instantiateViewController(withIdentifier: "SecondFloorVC") as! SecondFloor
   secondFloorVC.secondFloorDelegate = reference
   self.present(secondFloorVC, animated: true, completion: nil)
}

After typing message, we are sending message to FirstFloor. Then we are dismissing from SecondFloor.

@IBAction func sendMessageToFirstFloor(_ sender: UIButton) {
   if let delegate = secondFloorDelegate {
      delegate.shareMessageToFirstFloor(message: messageToFirstFloorTextView.text)
   }
   self.dismiss(animated: true, completion: nil)
}

Here ARC won’t deallocate SecondFloor instance. Because we have created a strong retain cycle between FirstFloor and SecondFloor.

var secondFloorVC: SecondFloor!
@IBAction func goToSecondFloor(_ sender: UIButton) {
   let reference = self
   secondFloorVC = UIStoryboard(name: "Main", bundle: nibBundle).instantiateViewController(withIdentifier: "SecondFloorVC") as! SecondFloor
   secondFloorVC.secondFloorDelegate = reference
   self.present(secondFloorVC, animated: true, completion: nil)
}

So we need to break the retain cycle to deallocate SecondFloor Instance. So we will see how to debug and break the retain cycle.

Debugging Memory issue

Usually in debug navigator you will be able to see references. Now I kept a break point in FirstFloor inside goToSecondFloor IBAction method.

Here you will able to see value types by displaying values and reference type with its memory address. Also you could see both the reference variable’s memory address and self’s memory address are same.

Now we are going to see how to debug memory issue. Click on the Debug Memory Graph option in the Debug Tab bar. Now you will be able to see the references we are maintaining in our project.

Here we could able to see AppDelegate method which is Singleton and with one SecondFloor instance which was not deallocated. If you click on the SecondFloor reference you will be able to see the retain cycle in the graphical representation.

Here we are having a strong retain cycle between FirstFloor and SecondFloor. So we need to break that retain cycle.

There is also another way to Debug Memory issue. For that first you need to clean the project folder(cmd + shift + k) and profile your project(cmd + I). Xcode has a inbuilt developer tool called Instruments. In that we are having Allocations to track virtual memory and heaps. Choose Allocations, In top left corner of Allocations tool choose your device and project you profiled and click on the record button. Instruments will start recording, you can perform the screen navigation in your device/simulator and stop recording. 

In Allocations, you will have different option to see the result like Statistics, Call Trees etc. Here I am choosing Statistics option to see the result. Now I am selecting Statistics option to debug.

Here you will able to see the number of columns and rows of the recorded result.

The third column Persistent Bytes represent total number of bytes currently used by our application. Persistent column represent number of references currently maintaining in our project. Transient column represents Number of references totally created by our project.

Now I am searching for SecondFloor’s reference by typing Floor in the bottom left search bar in Allocations. Now you will able to see filtered results. Currently we have one FirstFloor and SecondFloor instance persisting. You could also able to see three Transient instances of SecondFloor. Because I have totally visited four time SecondFloor Screen, three instances which will be no longer needed so ARC automatically deallocated it. One instance of SecondFloor which will never be deallocated.

You can also debug using call trees, for that choose main thread and you can debug using their hierarchy. Choose a method that taking higher bytes if you click on it you will able to see source code of that method. Now you switch to statistics mode you will able to see the instance.

Solving Memory Issue

var secondFloorDelegate: SecondFloorDelegate!

We have created a strong reference cycle between FirstFloor and SecondFloor. We know that ARC won’t increment reference count for weak or unowned reference. So we are making SecondFloor instance as weak.

weak var secondFloorVC: SecondFloor!

By making secondFloorVC instance as weak we could be able to break the reference Cycle.

Now we have seen how to debug a basic memory issue using Allocations in detail. In our next blog we will see in detail about some of the most common memory issue mistakes faced in the project.


Srikanth T,
iOS Development Team,
Mallow Technologies.

 

 

Leave a Reply

%d bloggers like this: