Ошибка NSURLSession Memory Утечки возникают при использовании веб-служб в IOS
Я создаю приложение, использующее веб-службу, и для получения информации из этой веб-службы я использую NSURLSession
и NSURLSessionDataTask
.
Теперь я нахожусь в фазе тестирования памяти, и я обнаружил, что NSURLSession
вызывает утечку памяти.
![This is not all of the leaks. It is all I could fit in the picture.]()
Это не все утечки. Это все, что я мог вместить в картину.
Ниже описано, как я настраиваю NSURLSession
и запрашиваю информацию из веб-службы.
#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 -
Вопрос:
Почему NSURLSession
вызывает утечку памяти? Я теряю силу NSURLSession
, когда я закончил с этим. Я также выпускаю любые свойства, которые мне не нужны, и установка сеанса на нуль (обратитесь к - (void) cleanupSession и - (void) endSessionAndCancelTasks).
Другая информация:
Утечки памяти происходят после завершения сеанса и "очистки". Иногда они также возникают после того, как я вытащил UIViewController
. Но все мои пользовательские объекты (GPPhotoRequest и GPSearch) и UIViewController освобождаются (я убедился, добавив NSLog).
Я старался не отправлять много кода, но мне казалось, что многое из этого нужно увидеть.
Пожалуйста, дайте мне знать, если вам нужна дополнительная информация. Помощь приветствуется.
Ответы
Ответ 1
После перечитывания Руководство по программированию URL-адреса URL-адреса выясняется, что я устанавливал свойство NSURLSession
на nil слишком рано.
Вместо этого мне нужно установить свойство NSURLSession
равным нулю после получения сообщения делегата URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
, что имеет смысл. К счастью, это была небольшая ошибка.
например.
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
{
if (error)
{
#ifdef DEBUG
NSLog(@"[GPPhotoRequest] Session Invalidation: %@", [error description]);
#endif
}
if ([session isEqual:_session])
{
[self cleanupSession];
}
}
Ответ 2
У меня была такая же "проблема", проблема с управлением памятью, когда я переключился на NSURLSession. Для меня, после создания сеанса и возобновления/запуска dataTask, я никогда не говорил сеансу очищать себя (т.е. Освобождать выделенную память).
// Ending my request method with only the following line causes memory leaks
[dataTask resume];
// Adding this line fixed my memory management issues
[session finishTasksAndInvalidate];
В docs:
После завершения последней задачи и завершения сеанса последнего вызова делегата будут нарушены ссылки на объекты делегата и обратного вызова.
Очистка моих сеансов фиксировала утечки памяти, передаваемые с помощью инструментов.
Ответ 3
Была та же проблема. Ответ @Jonathan не имеет смысла - мое приложение все еще просочилось в память. Я узнал, что установка свойства session в nil в URLSession:didBecomeInvalidWithError:
метод делегирования вызывает проблему. Пытался глубже заглянуть в Руководство по программированию URL-адреса URL. В нем говорится:
После аннулирования сеанса, когда все невыполнимые задачи были отменены или завершены, сеанс отправляет делегату сообщение URLSession: didBecomeInvalidWithError:. Когда этот метод делегата возвращается, сеанс предоставляет делегату сильную ссылку.
Я оставил метод делегата пустым. Но у недействительного свойства session
все еще есть указатель, когда я должен установить его nil
? Я просто установил это свойство слабым
// .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];
//...
}
Приложение перестало утечки памяти.