Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Downloading more than one file Using NSURLSession and also maintain their progress in background state

I have used NSURLSession to download a single file and it is working fine,Now i have to download three files in background and also have to manage their progress in UIProgress.My code for single downloading is below..

- (IBAction)startBackground:(id)sender 
{
    // Image CreativeCommons courtesy of flickr.com/charliematters
    NSString *url = @"http://farm3.staticflickr.com/2831/9823890176_82b4165653_b_d.jpg";
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
    self.backgroundTask = [self.backgroundSession downloadTaskWithRequest:request];
    [self setDownloadButtonsAsEnabled:NO];
    self.imageView.hidden = YES;
    // Start the download
    [self.backgroundTask resume];
}

- (NSURLSession *)backgroundSession
{
    static NSURLSession *backgroundSession = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.shinobicontrols.BackgroundDownload.BackgroundSession"];
        backgroundSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
    });
    return backgroundSession;
}

#pragma mark - NSURLSessionDownloadDelegate methods
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    double currentProgress = totalBytesWritten / (double)totalBytesExpectedToWrite;
    dispatch_async(dispatch_get_main_queue(), ^{
        self.progressIndicator.hidden = NO;
        self.progressIndicator.progress = currentProgress;
    });
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
    // Leave this for now
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
    // We've successfully finished the download. Let's save the file
    NSFileManager *fileManager = [NSFileManager defaultManager];

    NSArray *URLs = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
    NSURL *documentsDirectory = URLs[0];

    NSURL *destinationPath = [documentsDirectory URLByAppendingPathComponent:[location lastPathComponent]];
    NSError *error;

    // Make sure we overwrite anything that's already there
    [fileManager removeItemAtURL:destinationPath error:NULL];
    BOOL success = [fileManager copyItemAtURL:location toURL:destinationPath error:&error];

    if (success)
    {
        dispatch_async(dispatch_get_main_queue(), ^{
            UIImage *image = [UIImage imageWithContentsOfFile:[destinationPath path]];
            self.imageView.image = image;
            self.imageView.contentMode = UIViewContentModeScaleAspectFill;
            self.imageView.hidden = NO;
        });
    }
    else
    {
        NSLog(@"Couldn't copy the downloaded file");
    }

    if(downloadTask == cancellableTask) {
        cancellableTask = nil;
    } else if (downloadTask == self.resumableTask) {
        self.resumableTask = nil;
        partialDownload = nil;
    } else if (session == self.backgroundSession) {
        self.backgroundTask = nil;
        // Get hold of the app delegate
        SCAppDelegate *appDelegate = (SCAppDelegate *)[[UIApplication sharedApplication] delegate];
        if(appDelegate.backgroundURLSessionCompletionHandler) {
            // Need to copy the completion handler
            void (^handler)() = appDelegate.backgroundURLSessionCompletionHandler;
            appDelegate.backgroundURLSessionCompletionHandler = nil;
            handler();
        }
    }

 }

 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
 {
     dispatch_async(dispatch_get_main_queue(), ^{
        self.progressIndicator.hidden = YES;
        [self setDownloadButtonsAsEnabled:YES];
     });
 }
like image 770
Alok Chandra Avatar asked Mar 03 '14 09:03

Alok Chandra


1 Answers

You can have multiple NSURLSessionDownloadTask use the same NSSession and each are executed one after the other on the same mainQueue.

when successfully downloaded they call:

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location

If you download a MP4 of 100 mB and a pic of 10KB then they will return in different orders into this method.

So to track what session and what downloadTask has returned in this

you need to set a string identifier BEFORE you call the web service

To distinguish/track NSURLSessions you set

session.configuration.identifier

to distinguish NSURLSessionDownloadTask use

downloadTask_.taskDescription

downloadTask_.taskDescription =  [NSString stringWithFormat:@"%@",urlSessionConfigurationBACKGROUND_.identifier];

For example in my project I was downloading a number of user favourite videos and their thumbnails.

Each item had a id e.g.1234567 So I needed to make two calls for each favourite

so i created two identifiers

"1234567_VIDEO"
"1234567_IMAGE"

then called two ws calls and passed in the identifier in session.configuration.identifier

http://my.site/getvideo/1234567
"1234567_VIDEO"

http://my.site1/getimage/1234567
"1234567_IMAGE"

iOS7 will download the items in the background, app can go back to sleep When done it calls

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location

I then get

session.configuration.identifier
"1234567_IMAGE"

split it up and check the values

1234567_IMAGE
"1234567"
"_IMAGE"   > item at location is a MP4 so save as /Documents/1234567.mp4
"_VIDEO"   > item at location is a jpg so save as /Documents/1234567.jpg

If you have 3 urls to call you can have One NSURLSessionDownloadTask per NSSession

file 1 - NSSession1 > NSURLSessionDownloadTask1
file 2 - NSSession2 > NSURLSessionDownloadTask2
file 3 - NSSession3 > NSURLSessionDownloadTask3

This seems to work fine when the app is in the foreground. But I had problems when using BACKGROUND TRANSFER with BACKGROUND FETCH. The first NSSession > NSURLSessionDownloadTask1 would return and then none of the others would be called.

So safer to have multiple NSURLSessionDownloadTask in one NSSession1

file 1 - NSSession1 > NSURLSessionDownloadTask1
file 2 -            > NSURLSessionDownloadTask2
file 3 -            > NSURLSessionDownloadTask3

Be careful when doing this call NSSession finishTasksAndInvalidate not invalidateAndCancel

  //[session invalidateAndCancel];
   [session finishTasksAndInvalidate];

invalidateAndCancel will stop the session and not finish the other download tasks

like image 190
brian.clear Avatar answered Nov 18 '22 19:11

brian.clear