24

I am building an app that uses a web service and to get information from that web service I use NSURLSession and NSURLSessionDataTask.

I am now in the memory testing phase and I have found that NSURLSession is causing memory leaks.

This is not all of the leaks. It is all I could fit in the picture.

This is not all of the leaks. It is all that I could fit in the picture.

Below is how I setup the NSURLSession and request the information from the web service.

#pragma mark - Getter Methods

- (NSURLSessionConfiguration *)sessionConfiguration
{
    if (_sessionConfiguration == nil)
    {
        _sessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];

        [_sessionConfiguration setHTTPAdditionalHeaders:@{@"Accept": @"application/json"}];

        _sessionConfiguration.timeoutIntervalForRequest = 60.0;
        _sessionConfiguration.timeoutIntervalForResource = 120.0;
        _sessionConfiguration.HTTPMaximumConnectionsPerHost = 1;
    }

    return _sessionConfiguration;
}

- (NSURLSession *)session
{
    if (_session == nil)
    {
        _session = [NSURLSession
                    sessionWithConfiguration:self.sessionConfiguration
                    delegate:self
                    delegateQueue:[NSOperationQueue mainQueue]];
    }

    return _session;
}

#pragma mark -


#pragma mark - Data Task

- (void)photoDataTaskWithRequest:(NSURLRequest *)theRequest
{

#ifdef DEBUG
    NSLog(@"[GPPhotoRequest] Photo Request Data Task Set");
#endif

    // Remove existing data, if any
    if (_photoData)
    {
        [self setPhotoData:nil];
    }

    self.photoDataTask = [self.session dataTaskWithRequest:theRequest];

    [self.photoDataTask resume];
}
#pragma mark -


#pragma mark - Session

- (void)beginPhotoRequestWithReference:(NSString *)aReference
{
#ifdef DEBUG
    NSLog(@"[GPPhotoRequest] Fetching Photo Data...");
#endif

    _photoReference = aReference;

    NSString * serviceURLString = [[NSString alloc] initWithFormat:@"%@/json?photoreference=%@", PhotoRequestBaseAPIURL, self.photoReference];

    NSString * encodedServiceURLString = [serviceURLString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

    serviceURLString = nil;

    NSURL * serviceURL = [[NSURL alloc] initWithString:encodedServiceURLString];

    encodedServiceURLString = nil;

    NSURLRequest * request = [[NSURLRequest alloc] initWithURL:serviceURL];

    [self photoDataTaskWithRequest:request];

    serviceURL = nil;
    request = nil;
}

- (void)cleanupSession
{
#ifdef DEBUG
    NSLog(@"[GPPhotoRequest] Session Cleaned Up");
#endif

    [self setPhotoData:nil];
    [self setPhotoDataTask:nil];
    [self setSession:nil];
}

- (void)endSessionAndCancelTasks
{
    if (_session)
    {
#ifdef DEBUG
        NSLog(@"[GPPhotoRequest] Session Ended and Tasks Cancelled");
#endif

        [self.session invalidateAndCancel];

        [self cleanupSession];
    }
}

#pragma mark -


#pragma mark - NSURLSession Delegate Methods

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
#ifdef DEBUG
    NSLog(@"[GPPhotoRequest] Session Completed");
#endif

    if (error)
    {
#ifdef DEBUG
        NSLog(@"[GPPhotoRequest] Photo Request Fetch: %@", [error description]);
#endif

        [self endSessionAndCancelTasks];

        switch (error.code)
        {
            case NSURLErrorTimedOut:
            {
                // Post notification
                [[NSNotificationCenter defaultCenter] postNotificationName:@"RequestTimedOut" object:self];
            }
                break;

            case NSURLErrorCancelled:
            {
                // Post notification
                [[NSNotificationCenter defaultCenter] postNotificationName:@"RequestCancelled" object:self];
            }
                break;

            case NSURLErrorNotConnectedToInternet:
            {
                // Post notification
                [[NSNotificationCenter defaultCenter] postNotificationName:@"NotConnectedToInternet" object:self];
            }
                break;

            case NSURLErrorNetworkConnectionLost:
            {
                // Post notification
                [[NSNotificationCenter defaultCenter] postNotificationName:@"NetworkConnectionLost" object:self];
            }
                break;

            default:
            {

            }
                break;
        }
    }
    else {

        if ([task isEqual:_photoDataTask])
        {
            [self parseData:self.photoData fromTask:task];
        }
    }
}

- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
{
    if (error)
    {

#ifdef DEBUG
        NSLog(@"[GPPhotoRequest] Session Invalidation: %@", [error description]);
#endif

    }

    if ([session isEqual:_session])
    {
        [self endSessionAndCancelTasks];
    }
}

#pragma mark -


#pragma mark - NSURLSessionDataTask Delegate Methods

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{

#ifdef DEBUG
    NSLog(@"[GPPhotoRequest] Received Data");
#endif

    if ([dataTask isEqual:_photoDataTask])
    {
        [self.photoData appendData:data];
    }
}

#pragma mark -

Question: Why is NSURLSession causing these memory leaks? I am invalidating the NSURLSession when I am finished with it. I am also releasing any properties that I do not need and setting the session to nil (refer to - (void)cleanupSession & - (void) endSessionAndCancelTasks).

Other Information: The memory leaks occur after the session has completed and "cleaned up". Sometimes, they also occur after I have popped the UIViewController. But, all of my custom (GPPhotoRequest and GPSearch) objects and UIViewController are being dealloced (I've made sure by adding an NSLog).

I tried not to post to much code, but I felt like most of it needed to be seen.

Please let me know if you need any more information. Help is greatly appreciated.

Jonathan
  • 2,623
  • 3
  • 23
  • 38

4 Answers4

50

I had this same "leaky", memory management issue when I switched to NSURLSession. For me, after creating a session and resuming/starting a dataTask, I was never telling the session to clean itself up (i.e. release the memory allocated to it).

// Ending my request method with only the following line causes memory leaks
[dataTask resume];

// Adding this line fixed my memory management issues
[session finishTasksAndInvalidate];

From the docs:

After the last task finishes and the session makes the last delegate call, references to the delegate and callback objects are broken.

Cleaning up my sessions fixed the memory leaks being reported via Instruments.

John Erck
  • 9,478
  • 8
  • 61
  • 71
  • Also from the docs: "Important: Calling this method on the session returned by the sharedSession method has no effect." :( – Swindler May 26 '16 at 20:25
  • 3
    you made my day man :) I had 18 leaks and when added this invalidate method - 0 leaks – Stan Nov 27 '17 at 17:17
4

After rereading the URL Loading System Programming Guide it turns that I was setting the NSURLSession property to nil too early.

Instead, I need to set the NSURLSession property to nil AFTER I receive the delegate message URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error, which makes sense. Luckily, it was a minor mistake.

E.g.

- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
{
    if (error)
    {

#ifdef DEBUG
        NSLog(@"[GPPhotoRequest] Session Invalidation: %@", [error description]);
#endif

    }

    if ([session isEqual:_session])
    {
        [self cleanupSession];
    }
}
Jonathan
  • 2,623
  • 3
  • 23
  • 38
  • Reviving an old question, but I have this exact same issue, and your fix doesn't fix it for me? – Sammio2 Sep 09 '14 at 10:18
  • @Sammio2 look at my answer below. It may help you as it solved my issue. – Daniyar Jan 28 '15 at 07:22
  • Note that the Security framework allocations will still remain for 10 minutes after being initially charged to your app. Because the user cannot manually clear them no matter what you do. The system handles it, because Security. Watch those growths from Security library and see them disappear exactly 10 minutes after they first show up. See my answer here http://stackoverflow.com/questions/28223345/memory-leak-when-using-nsurlsession-downloadtaskwithurl/35757989#35757989 – CommaToast Mar 05 '16 at 19:38
1

Had the same issue. The @Jonathan's answer didn't make a sense - my app still leaked memory. I found out that setting the session property to nil in URLSession:didBecomeInvalidWithError: delegate method is causing the issue. Tried to look deeper into the URL Loading System Programming Guide. It says

After invalidating the session, when all outstanding tasks have been canceled or have finished, the session sends the delegate a URLSession:didBecomeInvalidWithError: message. When that delegate method returns, the session disposes of its strong reference to the delegate.

I left the delegate method blank. But the invalidated session property still have a pointer, when should I set it nil? I just set this property weak

// .h-file
@interface MyClass : NSObject <NSURLSessionDelegate>
{
  __weak NSURLSession *_session;
} 

// .m-file
- (NSURLSessionTask *)taskForRequest:(NSURLRequest *)request  withCompletionHandler:(void(^)(NSData *,NSURLResponse *,NSError *))handler
{
  if(!_session)
    [self initSession];
  //...
}

The app stopped leaking memory.

Daniyar
  • 2,975
  • 2
  • 26
  • 39
1

Please see my answer here: https://stackoverflow.com/a/53428913/4437636

I believe this leak is the same one I was seeing, and only happens when running network traffic through a proxy. My code was fine, but it turned out that an internal bug in the Apple API was causing the leak.

CPR
  • 753
  • 8
  • 19
  • I know this is probably not an answer to the question, but these weird leaks torment me for months. The problem was that I always used proxy when debugging the app. And then I see this answer and try without proxy. Bam, leaks disappeared. – Andreas777 Mar 08 '19 at 09:54
  • @andreas777 yes, I had exactly the same issue (see above). I have filed a radar with Apple, but they haven't replied yet. I filed it several months ago. – CPR Mar 08 '19 at 09:58