Задача загрузки NSURLSession не освобождает память

Из-за того, что клиент не может выполнить всего несколько загрузок на своем сервере за короткое время, а backgroundDownloadTaks были очень непоследовательными при таком большом количестве файлов (500-1000 загрузок), я решил использовать NSURLDownloadTask без фоновой NSURLSession.

С большим количеством файлов работает неплохо, но есть неудобство. Использование памяти всегда растет, пока я не получу предупреждение о памяти. Когда я получаю это, я отменяю ожидающие задачи и освобождаю NSURLCache, но память не освобождается, поэтому, когда вы возобновляете загрузку, вы получаете то же предупреждение о памяти.

Я не использую cancelWithResumeData для отмены задач.

это мой код

- (void) startDownloadFiles:(NSMutableArray*)arrayFiles
{
    if([[UIDevice currentDevice] isMultitaskingSupported])
    {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

            if (!self.session)
            {
                NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
                sessionConfiguration.HTTPMaximumConnectionsPerHost = 5;
                sessionConfiguration.timeoutIntervalForRequest = 0;
                sessionConfiguration.timeoutIntervalForResource = 0;
                sessionConfiguration.requestCachePolicy = NSURLCacheStorageNotAllowed;

                self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
                                                             delegate:self
                                                        delegateQueue:nil];

            }

            //Resetting session
            [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {

                for (NSURLSessionTask *_task in downloadTasks)
                {
                    [_task cancel];
                }

                [self.session resetWithCompletionHandler:^{                       
                    for (id<FFDownloadFileProtocol> file in self.selectedCatalogProducto.downloadInfo.arrayFiles)
                    {
                        if (cancel)
                            break; //Do not create more taks                        

                        if (![file isDownloaded])
                            [self startDownloadFile:file];

                    }                
                }];

            }];

        });

    }
}


- (void) startDownloadFile:(id<FFDownloadFileProtocol>)file
{
    if (![file isDownloading])
    {
        if ([file taskIdentifier] == -1
            && ! cancel)
        {
            NSURLSessionDownloadTask *task = [self.session downloadTaskWithURL:[file downloadSource]];

            if (task)
            {
                [file setDownloadTask:task];
                [file setTaskIdentifier:[file downloadTask].taskIdentifier];
                [[file downloadTask] resume];
            }
            else
            {
                NSLog(@"Error creando tarea para descargar %@", [file downloadSource]);
            }
        }
    }
}

#pragma mark - Auxiliar Methods

-(id<FFDownloadFileProtocol>)getFileDownloadInfoIndexWithTaskIdentifier:(unsigned long)taskIdentifier
{
    for (id<FFDownloadFileProtocol> file in self.selectedCatalogProducto.downloadInfo.arrayFiles)
    {
        if (file.taskIdentifier == taskIdentifier) {
            return file;
        }
    }

    return nil;
}

#pragma mark - NSURLSessionDownloadTaskDelegate

- (void) URLSession:(NSURLSession *)session
       downloadTask:(NSURLSessionDownloadTask *)downloadTask
       didWriteData:(int64_t)bytesWritten
  totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    if (totalBytesExpectedToWrite == NSURLSessionTransferSizeUnknown) {
        NSLog(@"Unknown transfer size");
    }
    else
    {
        // Locate the FileDownloadInfo object among all based on the taskIdentifier property of the task.
        id<FFDownloadFileProtocol> file = [self getFileDownloadInfoIndexWithTaskIdentifier:downloadTask.taskIdentifier];
        // Calculate the progress.
        file.downloadProgress = (double)totalBytesWritten / (double)totalBytesExpectedToWrite;
//        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
//            NSLog("%@ ; %f", [file fileName], [file downloadProgress]);
//        }];
    }
}

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    id<FFDownloadFileProtocol> file = [self getFileDownloadInfoIndexWithTaskIdentifier:downloadTask.taskIdentifier];

    if (file)
    {
        NSError *error;
        NSFileManager *fileManager = [NSFileManager defaultManager];

        NSURL *destinationURL = [[NSURL fileURLWithPath:tempPath] URLByAppendingPathComponent:[file fileName]];

        if ([fileManager fileExistsAtPath:[destinationURL path]]) {

            NSError *delError = nil;
            [fileManager removeItemAtURL:destinationURL error:nil];

            if (delError)
            {
                NSLog(@"Error borrando archivo temporal en %@", [destinationURL path]);
            }

        }

        BOOL success = [fileManager copyItemAtURL:location
                                            toURL:destinationURL
                                            error:&error];

        if (success) {

            // Change the flag values of the respective FileDownloadInfo object.

            file.isDownloading = NO;
            file.isDownloaded = YES;

            // Set the initial value to the taskIdentifier property of the file object,
            // so when the start button gets tapped again to start over the file download.

        }
        else
        {
            NSLog(@"Unable to copy temp file to %@ Error: %@", [destinationURL path], [error localizedDescription]);
        }

        if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateActive)
        {
            indexFile++;
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                [self numFilesDownloaded:indexFile];
            }];
        }
    }
}

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    id<FFDownloadFileProtocol> file = [self getFileDownloadInfoIndexWithTaskIdentifier:task.taskIdentifier];

    if (error != nil
        && error.code != -999)
    {
        //No se ha producido error o se ha cancelado la tarea bajo demanda
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{

            NSLog(@"Download: %@. \n Downlonad completed with error: %@", [task.response.URL absoluteString], [error localizedDescription]);

            if (!cancel)
            {
                NSString *alertBody = @"Se ha producido un error en la descarga, por favor reanúdela manualmente";

                if ([error.domain isEqualToString:@"NSPOSIXErrorDomain"] && (error.code == 1) )
                {
                    alertBody = @"Se ha interrumpido la descarga debido a que su iPad está bloqueado por código. Por favor reanude la descarga manualmente y evite que el iPad se bloquee";
                }

                // Show a local notification when all downloads are over.
                UILocalNotification *localNotification = [[UILocalNotification alloc] init];
                localNotification.alertBody = alertBody;
                [[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];

                [self errorDownloading:error.localizedDescription];
            }
        }];

    }
    else if (file)
    {
        NSLog(@"%@ download finished successfully.", [[file downloadSource] absoluteString]);

        file.taskIdentifier = -1;

        // In case there is any resume data stored in the file object, just make it nil.
        file.taskResumeData = nil;
        file.downloadTask = nil;
    }
    else if (cancel)
    {
        NSLog(@"Tarea cancelada");
    }

    if (self.selectedCatalogProducto.downloadInfo.arrayFiles.count == indexFile
        && !cancel)
    {
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            if (!complete)
            {
                complete = YES;
                [self downloadComplete];
            }
        }];
    }

    task = nil;
}

#pragma mark - Memory warning

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];

    if (_isDownloading)
    {
        [self storeCatalogProductInfo:self.selectedCatalogProducto andDownloadInfo:YES];
        [self stopDownloading];
    }

    [[NSURLCache sharedURLCache] removeAllCachedResponses];
    [self.session.configuration.URLCache removeAllCachedResponses];
}

А это два снимка использования памяти

Увеличение использования памяти при загрузке файловУвеличение использования памяти при загрузке файлов

Задачи загрузки остановлены, но память не освобожденаЗадачи загрузки остановлены, но память не освобождена

Почему я не могу освободить память?

Спасибо за любую помощь


person Rotten    schedule 04.12.2014    source источник
comment
Я бы начал с документации NSURLSession.   -  person Ben Affleck    schedule 04.12.2014
comment
Я прочитал всю документацию, но ничего не говорит о памяти. Только это Важно: объект сеанса сохраняет строгую ссылку на делегата до тех пор, пока ваше приложение явно не аннулирует сеанс. Если вы не аннулируете сеанс, в вашем приложении произойдет утечка памяти. Я сделал это, но память не освобождается   -  person Rotten    schedule 04.12.2014


Ответы (1)


Вам нужно вызвать метод invalidateAndCancel в вашем экземпляре NSURLSession, когда вы закончите его использовать, иначе произойдет утечка памяти.

person Ben Thielker    schedule 01.09.2015