Memory Leak with Memory Graph Debugger
Being an iOS developer, We should have excellent understanding about the memory management in iOS applications. So let’s understand what memory leak is and how it impacts user experience and performance of an application.
What is a Memory Leak?
A memory leak can be defined as a reference that is deleted from memory but not released by the owner and that reference can not be released, accessible and also not able to use. A memory leak is a little but serious problem that arises when your application doesn’t release memory that isn’t in use. These leaks can compound over time, resulting in decreased performance, crashes, and a disgruntled user base.
How does memory management work in Xcode?
Memory management in Xcode primarily depends on Automatic Reference Counting (ARC), a memory management mechanism used by Swift and Objective-C. Memory allocation and deallocation are automatically performed by ARC by monitoring object references within your code. Let’s understand how ARC works in Xcode.
ARC Working
The abbreviation ARC stands for Automatic Referencing Counting. ARC essentially tracks the lifetime of an object. It works with retain count; whenever any strong object is created, retain count is increased by 1, and when the object’s life is ended, retain count decreases by 1.
By Apple:
Every time you create a new class instance, ARC allocates a chunk of memory to store information about that instance. This memory holds information about the instance type and the values of any stored properties associated with that instance.
Additionally, when an instance is no longer needed, ARC frees up the memory used by that instance so that the memory can be used for other purposes instead. This ensures that class instances don’t take up space in memory when they’re no longer needed.
What is a Memory Graph Debugger?
The Memory Graph Debugger is a sophisticated tool included with Xcode. It assists developers in visualizing and analyzing the relationships between objects in memory during the runtime of a Swift or Objective-C application. It displays the number of references of any class that has been created. This tool is especially useful for detecting memory leaks, maintaining cycles, and understanding how memory is managed in your program. Before we begin, let’s review the fundamental setup needed for the memory graph debugger.
Enable Malloc Stack Logging
Malloc stack logging is a feature in macOS and iOS development environments that allows developers to capture stack traces of memory allocations and deallocations made by the malloc memory allocation function. It offers extensive information on the call stack during memory allocation and deallocation, which can be extremely useful for identifying memory-related problems including memory leaks, buffer overflows, and use-after-free errors. To enable malloc stack logging in Xcode, set environment variables that collect stack traces for memory allocations and deallocations. Here are the steps.
- Open your application in Xcode
- Then click on Schema -> Edit Scheme
- Select Run option in left menu
- Then select Diagnostics tab
- Under Memory Management, Select Malloc Stack Logging
- In drop down select All Allocation and Free History
- Then close popup
Select for Edit Schema
Enable Malloc Stack Logging
Pros and Cons of Memory Stack Logging
Pros: –
- Identifying Memory Leaks: Malloc stack logging captures precise information on memory allocations and deallocations, including stack traces.
- Detecting Overflows and Use-After-Free Errors: Malloc stack logging provides detailed stack traces that can assist in identifying buffer overflows and use-after-free issues, which are typical memory-related bugs.
- Understanding Memory Usage Patterns: By using this, you can gain insights into your application’s memory usage patterns.
Cons: –
- Performance Overhead: Enabling malloc stack logging may result in some performance overhead, particularly when capturing detailed stack traces for each memory allocation. This burden may influence the performance of your application, particularly in performance-sensitive scenarios.
- Increased Memory Usage: Malloc stack logging uses more memory to keep stack traces for each memory allocation. This can increase your application’s memory footprint, particularly if you enable comprehensive logging.
- Potential Privacy Concerns: Malloc stack logging captures detailed stack traces, which may include sensitive information such as function names and arguments. This information should be handled and stored securely, particularly in production contexts.
As we discussed, here are the pros and cons of enabling Malloc Stack Logging. So, always disable this after debugging the memory leaks by following the steps mentioned above. You just uncheck the Malloc Stack Logging check box.
How to work with Memory Graph?
The memory graph tool is incredibly simple to use and debug leaks. Let’s understand how.
1. Opening Memory Graph: Launch your program using the Memory Graph Debugger in Xcode’s debug mode. While your app runs, navigate to the Debug menu in Xcode and select “Debug Memory Graph.” As given in the image below:
Opening Memory Graph
2. Graphical Representation:- As we already discussed, it visually displays the relationship between objects. The Memory Graph Debugger shows Objects in your application as nodes in a graph. Relationships between items are represented as connections or edges between nodes.
Graphical Representation
3. Navigation and Interaction:- To investigate various aspects of the item relationships, you can pan and zoom in and out of the graph. Clicking on a node displays information about that item, such as its class, characteristics, and relationships.
Navigation to an object
4. Reference Count:- The Memory Graph Debugger displays reference counts for each object, allowing developers to understand how many references are kept at any given time. As the program runs, developers may track changes in reference counts in real-time. As we can also verify the above screenshot.
5. Object Details:- When a node is clicked, an inspection panel with comprehensive details about the chosen object is displayed. Developers may inspect the characteristics and relationships of the item, which helps them understand its purpose in the application.
6. Identifying Retain Cycle:- The Memory Graph Debugger can detect possible difficulties, such as retain cycles, which arise when objects refer to each other in a way that restricts automatic deallocation. Developers can identify loops in the graph that indicate possible retain cycles. Let’s understand it by images.
7. Leak Detection:- The Memory Graph Debugger is useful for detecting memory leaks by viewing objects that endure in memory after they should have been deallocated. Developers can follow the relationships between items to determine the primary causes of memory leaks.
Retain cycle code
Retain cycle graph
As shown in the images above, NewViewController contains 7 objects that have not been released because the above code aggressively captured Self. So, when we dismiss NewViewController, it will not be deallocated. Let’s see how we resolve this issue.
As demonstrated in the screenshot above, we have captured self as weak, and we can also see that when NewViewController is dismissed, it is correctly deinit. So, there is no reference to NewViewController count on the left panel.
Conclusion
The debugging environment in Xcode is smoothly linked with the Memory Graph Debugger. When debugging their code, developers can switch to it to find and fix memory-related problems. To ensure optimal memory management in their applications, developers must comprehend the Memory Graph and know how to use the Memory Graph Debugger. It provides information about how objects behave during runtime, helps locate problems like memory leaks and retain cycles, and is essential for maximizing application performance.
You can find the example in the repository below- MemoryLeakExample