How to resume a download task by using NSURLSession

How to resume a download task by using NSURLSession when user exit app directly last time?

Accepted Reply

Well,I work out!🙂

Here is the solution:

In foreground starts task,then switch to background, after a few seconds,app suspend, you swipe off the app,app will terminated.But the download local file which in CACHES still exist.If you relaunch app,it will execute didCompleteWithError,error contains resumeData.Since it will execute didCompleteWithError and local file where in CACHES still exist,so check the localPath of resumeData,if not exist,move file from CACHES to localPath.Then create a downloadTaskWithResumeData, resume it .That is it .

The delegate method!

-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)downloadCompletedTask didCompleteWithError:(NSError *)error
{
    NSLog(@"Session %@ download task %@ finished downloading with error %@\n",session, downloadCompletedTask, error);
    if (error) {
        NSData* resumeData = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData];
       if ([self __isValidResumeData:resumeData])
        {
            self.m_downloadTask = [self.m_backgroudSession downloadTaskWithResumeData:resumeData];
            [self.m_downloadTask resume];
        }
    }
}

Check the localPath of resumeData

- (BOOL)__isValidResumeData:(NSData *)data{
    if (!data || [data length] < 1) return NO;

    NSError *error;
    NSDictionary *resumeDictionary = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:NULL error:&error];
    if (!resumeDictionary || error) return NO;

    NSString *localTmpFilePath = [resumeDictionary objectForKey:@"NSURLSessionResumeInfoLocalPath"];
    if ([localTmpFilePath length] < 1) return NO;

    BOOL result = [[NSFileManager defaultManager] fileExistsAtPath:localTmpFilePath];

    if (!result) {
        NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
        NSString *localName = [localTmpFilePath lastPathComponent];
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
        NSString *cachesDir = [paths objectAtIndex:0];
        NSString *localCachePath = [[[cachesDir stringByAppendingPathComponent:@"com.apple.nsurlsessiond/Downloads"]stringByAppendingPathComponent:bundleIdentifier]stringByAppendingPathComponent:localName];
        result = [[NSFileManager defaultManager] moveItemAtPath:localCachePath toPath:localTmpFilePath error:nil];
    }
    return result;
}

Replies

I create a downloadTask by using backgroundSession type.If user terminate the app last time,I hope it can resume data when user lauch the app and download the same the task .How should I do to complete it?


Create a backgroundSession:

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:IDENTIFIER];
self.m_backgroudSession = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];

Create a downloadTask:

elf.m_urlRequest = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:[timeout doubleValue]];
self.m_downloadTask = [self.m_backgroudSession downloadTaskWithRequest:self.m_urlRequest];

Use delegate calls:

-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
    NSLog(@"Background URL session %@ finished events.\n", session);
   
    if (session.configuration.identifier)
        [self callCompletionHandlerForSession: session.configuration.identifier];
}
- (void) addCompletionHandler: (CompletionHandlerType) handler forSession: (NSString *)identifier
{
    if ([self.completionHandlerDictionary objectForKey: identifier]) {
        NSLog(@"Error: Got multiple handlers for a single session identifier.  This should not happen.\n");
    }
   
    [self.completionHandlerDictionary setObject:handler forKey: identifier];
}
- (void) callCompletionHandlerForSession: (NSString *)identifier
{
    CompletionHandlerType handler = [self.completionHandlerDictionary objectForKey: identifier];
   
    if (handler) {
        [self.completionHandlerDictionary removeObjectForKey: identifier];
        NSLog(@"Calling completion handler.\n");
       
        handler();
    }
}
-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler
{
    NSURLSessionConfiguration *backgroundConfigObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier];
   
    NSURLSession *backgroundSession = [NSURLSession sessionWithConfiguration: backgroundConfigObject delegate:self delegateQueue: [NSOperationQueue mainQueue]];
   
    NSLog(@"Rejoining session %@\n", identifier);
   
    [self addCompletionHandler: completionHandler forSession: identifier];
}

I have tried many times.I find one thing:

In foreground starts task,then switch to background, after a few seconds, I swipe off the app,app will terminated.But the download local file which in CACHES directory still exist.If I relaunch app,it will execute didCompleteWithError immediately,error contains resumeData.BTW,session value changed.So I resume downloadTask in the delegate method.

-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)downloadCompletedTask didCompleteWithError:(NSError *)error
{
    NSLog(@"Session %@ download task %@ finished downloading with error %@\n",session, downloadCompletedTask, error);
    if (error) {
        NSData* resumeData = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData];
        if (resumeData) {
            self.m_downloadTask = [self.m_backgroudSession downloadTaskWithResumeData:resumeData];
            [self.m_downloadTask resume];
        }
    }
}

But bug console tells me that Invalid resume data for background download. Background downloads must use http or https and must download to an accessible file.

Anyone's help would be appreciated!

Well,I work out!🙂

Here is the solution:

In foreground starts task,then switch to background, after a few seconds,app suspend, you swipe off the app,app will terminated.But the download local file which in CACHES still exist.If you relaunch app,it will execute didCompleteWithError,error contains resumeData.Since it will execute didCompleteWithError and local file where in CACHES still exist,so check the localPath of resumeData,if not exist,move file from CACHES to localPath.Then create a downloadTaskWithResumeData, resume it .That is it .

The delegate method!

-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)downloadCompletedTask didCompleteWithError:(NSError *)error
{
    NSLog(@"Session %@ download task %@ finished downloading with error %@\n",session, downloadCompletedTask, error);
    if (error) {
        NSData* resumeData = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData];
       if ([self __isValidResumeData:resumeData])
        {
            self.m_downloadTask = [self.m_backgroudSession downloadTaskWithResumeData:resumeData];
            [self.m_downloadTask resume];
        }
    }
}

Check the localPath of resumeData

- (BOOL)__isValidResumeData:(NSData *)data{
    if (!data || [data length] < 1) return NO;

    NSError *error;
    NSDictionary *resumeDictionary = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:NULL error:&error];
    if (!resumeDictionary || error) return NO;

    NSString *localTmpFilePath = [resumeDictionary objectForKey:@"NSURLSessionResumeInfoLocalPath"];
    if ([localTmpFilePath length] < 1) return NO;

    BOOL result = [[NSFileManager defaultManager] fileExistsAtPath:localTmpFilePath];

    if (!result) {
        NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
        NSString *localName = [localTmpFilePath lastPathComponent];
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
        NSString *cachesDir = [paths objectAtIndex:0];
        NSString *localCachePath = [[[cachesDir stringByAppendingPathComponent:@"com.apple.nsurlsessiond/Downloads"]stringByAppendingPathComponent:bundleIdentifier]stringByAppendingPathComponent:localName];
        result = [[NSFileManager defaultManager] moveItemAtPath:localCachePath toPath:localTmpFilePath error:nil];
    }
    return result;
}