7

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];
     });
 }
Undo
  • 25,519
  • 37
  • 106
  • 129
Sishu
  • 1,510
  • 1
  • 21
  • 48
  • 2
    So... what is your question? – i_am_jorf Mar 04 '14 at 00:31
  • this code is working for a single download if i have to download three files how will i manage ..i m not getting ..Please help for multiple files. – Sishu Mar 04 '14 at 06:37
  • If you can do it for one file, what difficulty are you having extending this to further downloads? – Abizern Mar 04 '14 at 08:19
  • like in the code it is creating a background session NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.shinobicontrols.BackgroundDownload.BackgroundSession"]; how to handle it for multiple files do i need a different bundle identifier each time and also problem with the progress bar it is updating a progress bar always.. – Sishu Mar 04 '14 at 08:38
  • i have created the classes for this and passing my progress bar there but is it not updating and also when i goes off from that class and rerun that code it working strangely..if u have time i can share my code sample..thnaks for viewing question – Sishu Mar 04 '14 at 08:40
  • 3
    it's sad to see a lot of useless comment like "What is your question" man... the question is absolutely clear and "if you can do it for one file, what difficult are you having extending to further downloads". If you know how to solve this problem why don't you just write down an answer? – MatterGoal Jul 10 '14 at 16:52
  • @MatterGoal i have successfully implemented it for one downloading but when i try to make it for various downloading it is not working,progress bar not updating. – Sishu Jul 11 '14 at 03:24

1 Answers1

5

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

brian.clear
  • 5,277
  • 2
  • 41
  • 62
  • How does NSURLSession let you know when all the files have finished downloading without the app having to be backgrounded? The delegate method does get called for when all the download tasks finish, but only when I background my app. I need it for when the app is still running. – NYC Tech Engineer May 14 '15 at 15:30
  • @NYCTechEngineer add all tasks to an array, then use let downloadsCompleted = (downloadTasks.indexOf({ $0.state == NSURLSessionTaskState.Running }) == nil) ? true : false with indicate they have completed. – DogCoffee Aug 22 '15 at 15:40