NSURLSession downloadTask не освобождава памет

Тъй като клиентът не може да внедри само няколко изтегляния в своя сървър за кратко време и 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