6
votes

Due to the customer can't implement just few downloads in his server in a short time and backgroundDownloadTaks were very inconsistent when there are so much files (500-1000 downloads) I decide to use NSURLDownloadTask without background NSURLSession.

It works quite well with a large amount of files, but there is an inconvenience. Memory usage is always growing untill I get a memory warning. When I get it I cancel pending tasks and free NSURLCache but memory is not released so when you resume the downloads you get the same memory warning.

I'm not using cancelWithResumeData for cancelling the tasks.

This is my code

- (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];
}

And these are two snapshot of memory usage

Memory usage increasing when files are downloading Memory usage increasing when files are downloading

Download tasks are stopped but memory is not released Download tasks are stopped but memory is not released

Why can't I release memory?

Thanks for any help provided

1
I would start with NSURLSession documentation.Rob Zombie
I've read whole of the documentation but do not says nothing about memory. Only that "Important: The session object keeps a strong reference to the delegate until your app explicitly invalidates the session. If you do not invalidate the session, your app leaks memory." I did it but no memory is releasedRotten

1 Answers

2
votes

You need to call the invalidateAndCancel method on your NSURLSession instance when you're done using it, otherwise it will leak memory.