Embrace’s iOS and Android SDKs are now built on OpenTelemetry!

Read the release
memory leaks

Best practices for handling memory leaks in iOS shopping apps

Learn about the technical causes of memory leaks as well as best practices for preventing them and steps you can take today.

In iOS apps, memory leaks occur when memory is allocated but never released back to the system when no longer in use or needed by the app.

Memory leaks can cause a number of problems in your user experience, including slow app performance, crashes, and battery drain.

While every iOS app is susceptible to memory leaks, they do tend to be more common in memory-intensive apps. For example, eComm and retail apps tend to use a lot of memory through the frequent loading of high-resolution images and videos.

In that regard, balancing the need to showcase products with rich visuals, while remaining within iOS memory limits, is the essential challenge mobile teams need to overcome in order to deliver a smooth user experience.

Technical causes of memory leaks in iOS apps

Memory leaks can occur when resources are not properly released, leading to increased memory usage. This can result in app slowdowns and/or crashes.

While automatic reference counting (ARC) on iOS eliminates most of the monotony of manual memory management, there are several ways that costly memory leaks can surface in your iOS apps. For example:

Unused memory

This can happen due to coding errors, conditional logic, or overlooked scenarios. For example, if a programmer allocates memory for a variable but forgets to assign a value to it or if a certain branch of code that should release allocated memory is never executed, it can lead to unused memory and memory leaks.

Caching issues

Improper cache management can result in memory leaks. You must remove or replace cached objects and data when they are no longer needed. Otherwise, they will continue to occupy memory indefinitely. This can occur due to programming errors, such as failing to update the cache when data changes or not implementing a mechanism to invalidate outdated cache entries. Instead of using a dictionary to hold onto cached items, use Apple’s provided `NSCache` for a good in-memory cache solution.

External resources

Interacting with external resources like databases, files, or network connections can also contribute to memory leaks if they are not properly released or closed. An example is opening a file or network connection but failing to close it after use. In that case, the associated memory resources may not be released. Similarly, if database connections are not properly closed or released, it can result in memory leaks over time.

Programming language-specific issues

Different programming languages have their own mechanisms that can contribute to memory leaks. Languages like C or C++ require manual memory management, and if deallocation is not performed correctly using functions like free() or delete, it can result in memory leaks. On the other hand, languages like Java or C# use automatic memory management through garbage collection, which can also have its challenges. For example, accidentally creating cyclic references between objects — where objects reference each other to prevent them from being garbage collected — can lead to memory leaks.

Best practices for addressing memory leaks in your iOS app

There are a number of ways to prevent memory leaks in your iOS app. Start with these best practices.

Track memory usage

Many platforms provide tools and utilities specifically designed for monitoring memory usage. For example, tools such as Instruments, Heap, and Leaks can be used to detect memory leaks and analyze memory patterns.

Handle cleanup operations gracefully

Always remember to deallocate resources, save data, release memory, and close connections/files at critical points. When an application is about to terminate, it is recommended to save essential state information as soon as possible to ensure that important data is not lost. You can accomplish this by implementing methods like applicationWillTerminate in the UIApplicationDelegate protocol on iOS.

Handle strong and weak references properly

Properly managing strong and weak references is crucial for avoiding memory leaks and maintaining optimal memory usage. Use strong references only when necessary for object lifecycle management. Strong references create a retain cycle where objects reference each other, preventing them from being deallocated even when they are no longer needed. In scenarios where you want to avoid memory leaks, such as delegate patterns, use weak references. Weak references do not increase the reference count, allowing objects to be deallocated when they are no longer referenced strongly. Using weak references appropriately can break retain cycles and ensure that memory is efficiently managed.

By following the recommended practices for memory allocation and deallocation, you can minimize the occurrence of memory leaks in your code. Let’s consider an example involving a NetworkRequest class that performs network requests in the code snippet below.

protocol NetworkRequestDelegate: AnyObject {       
    func didCompleteRequest(data: Data)   
}   

class NetworkRequest {       
    weak var delegate: NetworkRequestDelegate?       
    func performRequest() { // Simulating network request completion           
        if let responseData = “Response Data”.data(using: .utf8) {
            delegate?.didCompleteRequest(data: responseData)           
        } 
    } 
}  

class ViewController: NetworkRequestDelegate {       
    let networkRequest = NetworkRequest()       
    func startRequest() {           
        networkRequest.delegate = self           
        networkRequest.performRequest()       
    }       
    
    func didCompleteRequest(data: Data) {           
        let responseString = String(data: data, encoding: .utf8)           
        print(“Received response data: \(responseString ?? “”)”)       
    } 
}  

// Sample usage   
let viewController = ViewController()   
viewController.startRequest()

In this class, there is a weak reference delegate property of type NetworkRequestDelegate. By using a Weak reference for the delegate, the NetworkRequest instance does not keep a strong reference to the delegate. This ensures that the delegate, typically a ViewController, can be deallocated when no longer needed, preventing a strong reference cycle and potential memory leaks.

How to start addressing memory leaks today

Going beyond best practices, here are three steps you can take today to better address memory leaks in your iOS app.

Improve automated memory leak testing with Xcode (for Swift)

Automated memory leak testing is an essential practice in Swift-based projects. It involves creating targeted tests that automatically detect and terminate instances that could potentially cause memory leaks. Xcode provides built-in support for memory leak testing through XCTest, the testing framework for Swift and Objective-C. By writing specific test cases that exercise the code paths where memory leaks are likely to occur, you can enhance the efficiency and effectiveness of memory leak detection.

Use available memory management tools

  • Heap analysis: The heap command-line tool in Xcode allows you to inspect your application’s memory allocation and deallocation patterns. By analyzing heap snapshots, you can identify memory blocks not being properly released or deallocated, indicating potential memory leaks or inefficient memory usage.
  • Leaks detection: The leaks command-line tool in Xcode is specifically designed to detect and analyze memory leaks automatically. It scans your application’s memory and identifies objects still in memory despite being no longer reachable. This tool helps you pinpoint memory leaks accurately and provides detailed information about the leaked objects.
  • Instruments: Xcode’s Instruments tool offers a comprehensive set of features for memory analysis and allows you to monitor memory allocations and deallocations in real-time, helping you identify areas of excessive memory usage. The VM Tracker instrument tracks virtual memory usage, providing insights into memory pressure and potential leaks. Additionally, the Zombies instrument helps identify instances where objects are accessed after deallocation, which often cause crashes or unpredictable behavior.
  • Memory graph debugger: This powerful tool within Xcode allows you to visualize the object graph and understand the relationships between objects in memory. It helps you track object ownership and identify strong reference or retention cycles that can lead to memory leaks.

Custom diagnostic configuration

Xcode allows you to set up custom diagnostic configurations within your development scheme to enable or disable specific memory management options. For example, you can configure your scheme to include “malloc stack logging” during development. This feature records the stack trace associated with each memory allocation, helping you track down memory-smashing bugs, heap corruption, and other memory-related issues.

Learn more about improving your shopping app’s performance

Memory leaks are just one kind of issue that can impact your shopping app’s performance and create a painful user experience for your customers.

From fashion, to e-commerce, to home hardware, Embrace has helped some of the biggest names in mobile shopping optimize their apps. In that time, we’ve come across dozens of common issues that impact performance.

To help you build better iOS shopping experiences, we’ve compiled a list of 13 ways you can improve performance, in a comprehensive eBook that covers technical causes, best practices, and actionable steps you can take today.

Learn more about improving your iOS shopping app’s performance by downloading our eBook here.

Embrace 13 ways to improve your iOS shopping app

Download our ebook on common performance gaps, technical causes, and best practices.

Get the eBook

Build better mobile apps with Embrace

Find out how Embrace helps engineers identify, prioritize, and resolve app issues with ease.