Channels ▼
RSS

Design

Memory Leaks in iOS 7


With iOS 7, Apple introduced new networking APIs to replace the traditional NSURLConnection class for creating network requests and handling network responses. Built on top of NSURLConnection is the new NSURLSession API, which offers rich functionality for setting session-level configuration options, performing background processing (even if the application crashed), easier sending of network requests, and receiving responses using asynchronous blocks versus traditional delegate patterns. While these new APIs are easier to use and follow newer patterns that Apple incorporates within its other APIs, if developers follow Apple's documentation and other mainstream tutorials, they may find they are leaking memory, and eventually applications can crash due to memory pressure.

Application Example

Before I show where the memory leak occurs, let's create a sample application and use Apple's URL Loading System Programming Guide for instruction. Specifically, let's use the example code in the section "Fetching Resources Using System-Provided Delegates." I will modify the example code slightly to PUT a photo to a site using a REST service.

With the example code, I still need to create a NSURLSessionConfiguration object. NSURLSessionConfiguration allows me to set up certain parameters about future requests that are made for a session. It can set HTTP header values, caching properties, permit cellular access, and more.

For the NSURLSessionConfiguration object, I will use the defaultSessionConfiguration that Apple provides. Apple's documentation on the default session configuration reads: "The default session configuration causes the session to behave similarly to an NSURLConnection object in its standard configuration. If you are porting code from NSURLConnection, use this method to obtain an initial configuration and then customize the returned object as needed."

Based on this description, it's a perfect fit, especially if you are porting over an iOS 6 application. You could add additional configuration details to the NSURLSession, such as restricting cellular access, but I will just return the default configuration in this example:

-(NSURLSessionConfiguration *)sessionConfiguration {
    return [NSURLSessionConfiguration
               defaultSessionConfiguration];
}

Our session configuration is fairly simple. However, based on Apple's documentation, it should be sufficient.

For uploading the photo, I wrapped the example code provided by Apple in a method that takes image data and an identifier for the image. I made slight adjustments to the code as follows:

  1. Created a NSURLRequest as a PUT request with a given URL
  2. Changed the task type to NSURLSessionUploadTask, which subclasses the original NSURLSessionDataTask.
  3. Invalidated the session after all tasks are finished

The last change I made (item 3 above) is calling finishTasksAndInvalidate. Making this call is crucial; however, it is not emphasized enough in documentation. If you do not invalidate your session, it will leak memory. That being said, this is not the memory leak we are uncovering.

-(void)putPhoto:(NSData *)imageData usingIdentifier:(NSString *)identifier {
    // 1 - Setup Session
    NSURLSession *delegateFreeSession = 
        [NSURLSession sessionWithConfiguration:[self sessionConfiguration]

    delegate:nil
    delegateQueue:[NSOperationQueue mainQueue]];

    NSURL *syncPhotoUrl = 
        [NSURL URLWithString:[NSString 
            stringWithFormat:@"http://localhost/sync/photo/%@",
            identifier]];
    
    // 2 - Setup Request
    NSMutableURLRequest *request = 
        [[NSMutableURLRequest alloc] initWithURL:syncPhotoUrl];
    [request setHTTPMethod:@"PUT"];
    
    // 3 - Upload Photo
    [[delegateFreeSession 
        uploadTaskWithRequest:request
        fromData:imageData
        completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) 
        {
             NSLog(@"Got response %@ with error %@.\n", response, error);
             NSLog(@"DATA:\n%@\nEND DATA\n",
                   [[NSString alloc] initWithData:data 
                       encoding: NSUTF8StringEncoding]);
        }
    ] resume];
    
    
    // 4 - Need to invalidate session to ensure no memory is leaked
    [delegateFreeSession finishTasksAndInvalidate];
}

If the above code (which contains only slight modifications to the code provided by Apple) is run, it will eventually crash due to memory pressure. Additionally, if you look at other well-known NSURLSession tutorials out there (Ray Wenderlich and tuts+, for example), you will find that this code does not deviate from the tutorials and recommendations posted by Apple (see NSURLSession Class Reference and Using NSURLSession). To show the leak, let's profile this test application in Instruments.

Discovering the Memory Leak

If I run the application in Instruments using the "Allocations" instrument, I see that memory is allocated and released: However, not all of the memory that allocated is released. You notice this by observing the peaks and valleys in memory consumption where it never gets back down to the original utilization levels after a photo is uploaded to the server (Figure 1).

iOSmemoryleak
Figure 1: Initial memory profile.

Also, if you look at the defaultSessionConfiguration for NSURLSessionConfiguration, you will find that the default disk capacity is 2MB and the default memory capacity is 4MB:

(lldb) posessionConfiguration.URLCache.diskCapacity
20971520

(lldb) posessionConfiguration.URLCache.memoryCapacity
4194304

These values are relatively small to suspect that the application would crash due to memory pressure. Every time you create a NSURLSessionConfiguration and provide it to a new NSURLSession, you would expect that when the NSURLSession is destroyed, the part of it owning the session configuration object it would be destroyed as well. However by examining Instruments, we find the number of NSURLSessionConfiguration objects continue to increase (Figure 2):

iOSmemoryleak
Figure 2: Examining Instruments.

From this capture, there should not be 42 active session configurations while this application is running. After each NSURLSession is completed, these should be deallocated. Now, what Instruments shows as far as memory consumption is somewhat misleading; clearly, we can see that the allocated memory is increasing at a level that would end up crashing the application due to memory pressure. (Figure 3 shows the memory usage after little more than one minute of use.)

iOSmemoryleak
Figure 3: A more detailed look at the memory leak.

For this sample application, we have the benefit of being able to detect the leak because of the large amount of data that we are sending. However, applications that transmit a smaller amount of data may not know they are leaking memory because the leak is at a smaller scale and may go unnoticed. This may be why this issue is not well known today — our application is transmitting data at a large scale, however over time, even a small application can crash if appropriate measures (which I'll outline shortly) aren't taken.

Continuing to dig into the issue, look at the top memory allocation calls (Malloc). You can see that there is a large amount of memory being allocated (Figure 4):

iOSmemoryleak
Figure 4: Checking Malloc.

Here, we have 20 living memory allocations taking 108.36 MB, and none of these were released. Additionally, in the next row, we still have 18 living memory allocations taking 49.85 MB. In this example, there were 38 photos that were synced to the server. Discovering their owners of these is no easy task and there is no direct way of finding them in Instruments. However, considering that the memory climbs with network requests, if we dig around enough for what network-related objects are not being released, we find that there are cache entries that are still living and never released.

iOSmemoryleak
Figure 5: Cache entries still living.

Caching is done as part of the defaultSessionConfiguration we get from NSURLSessionConfiguration. Even though the cache on disk has a capacity of 2MB and the cache in memory has a capacity of 4MB, our memory consumption grows way beyond that. Unbeknown to the developer, these entries will continue to grow even when the NSURLSession object that was created has been deallocated. The question to ask is, "why is there a cache held in memory for an object we don't have a reference to?"  This is the key issue and it's where the memory leak occurs.

Who is Vulnerable?

An application might not crash due to memory pressure if it consistently sends requests to the same set of URLs and has a limited number of URLs in this set. The crash depends on the number of unique URLs that the application sends requests to and the size of data in that request and response pair. The reason for this is that for each URL that the application sends a request to, it stores a cached request and response. If it already has a cached entry, it does not create another entry for that pair. Because of this, applications that use RESTful services where data is uploaded to a server using a PUT request with a unique path are at most danger, since that URL will constantly change. Even applications that do not use RESTful services are not in the clear. If they have a set of URLs that they post data to, their application might not crash, but the memory that they have available will be constrained by the amount of memory that the cache is consuming for those URLs.

Workaround the Memory Leak

There are several ways to work around the memory leak. The most straightforward way is to configure your defaultSessionConfiguration with a NSURLCache object that has a memory capacity of 0. The following code reflects this configuration update:

-(NSURLSessionConfiguration *)sessionConfiguration {
    NSURLSessionConfiguration *config = 
         [NSURLSessionConfiguration defaultSessionConfiguration];

    config.URLCache = [[NSURLCache alloc] initWithMemoryCapacity:0 
                                          diskCapacity:0 
                                          diskPath:nil];

    return config;
}

With this simple update, you will not cache request/response data for that session (which, if you are calling Web services that deal with dynamic data, should cause no disadvantage).

Warning: From the documentation on NSURLSessionConfiguration, you may decide to use an ephemeralSessionConfiguration. Please note that this ensures only that cache is not written to disk; However, the cache entries will still be written to memory.

Conclusion

While Apple's new networking APIs are very powerful, if a developer doesn't understand how memory is being allocated and released, the application can leak memory and eventually crash due to memory pressure. I've identified this as a leak, since the developer does not have a reference to the consumed memory to release it. Also, due to the use of automatic reference counting (ARC) in Mac OS, the developer would assume that when the session is deallocated from memory, the corresponding cache in memory would be deallocated as well. As I have demonstrated, this is not the case. Fortunately, a work around does exist.


Brian Krupp is a software developer at Hyland Software and a Doctoral Candidate in the Department of Electrical and Computer Engineering at Cleveland State University's Washkewicz College of Engineering. His current research focuses on enhancing the security and privacy of mobile operating systems, specifically without requiring modification to the operating system.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video