NSURLSession Memory Leaks occur when using web services in IOS
ios, memory-leaks, nsurlsession
Solution
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];
}
}
Problem
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 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.