This method sets the background object.
- (void) downloadWithURL: (NSMutableArray *)urlArray pathArr: (NSMutableArray *)pathArr mediaInfo: (MediaInfo *)mInfo { bgDownloadMediaInfo = mInfo; reqUrlCount = urlArray.count; dict = [NSDictionary dictionaryWithObjects:pathArr forKeys:urlArray]; mutableDictionary = [dict mutableCopy]; backgroundConfigurationObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"XXXXX"]; backgroundConfigurationObject.sessionSendsLaunchEvents = YES; backgroundConfigurationObject.discretionary = YES; backgroundSession = [NSURLSession sessionWithConfiguration: backgroundConfigurationObject delegate: self delegateQueue: [NSOperationQueue currentQueue]]; self.requestUrl = [urlArray objectAtIndex:0]; download = [backgroundSession downloadTaskWithURL:self.requestUrl]; [download resume]; }
These are the completion handlers.
#pragma Mark - NSURLSessionDownloadDelegate - (void)URLSession: (NSURLSession *)session downloadTask: (NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL: (NSURL *)location { LogDebug(@"Download complete for request url (%@)", downloadTask.currentRequest.URL); NSString *temp = [mutableDictionary objectForKey:downloadTask.currentRequest.URL]; NSString *localPath = [NSString stringWithFormat: @"%@",temp]; NSFileManager *fileManager = [NSFileManager defaultManager]; NSURL *destinationURL = [NSURL fileURLWithPath: localPath]; NSError *error = nil; [fileManager moveItemAtURL:location toURL:destinationURL error:&error]; LogDebug(@"Moving download file at url : (%@) to : (%@)", downloadTask.currentRequest.URL, destinationURL); reqUrlCount --; downloadSegment ++; // Handover remaining download requests to the OS if ([finalUrlArr count] != 0) { // remove the request from the array that got downloaded. [finalUrlArr removeObjectAtIndex:0]; [finalPathArr removeObjectAtIndex:0]; if ([finalUrlArr count] > 0) { // proceed with the next request on top. self.requestUrl = [finalUrlArr objectAtIndex:0]; download = [backgroundSession downloadTaskWithURL:self.requestUrl]; [download resume]; } } if ([adsArray count] > 0) { adsArrayCount --; // delegate back once all the ADs segments have been downloaded. if (adsArrayCount == 0) { for (int i = 0; i < [adsArray count]; i++) { NSArray *ads = [adsArray objectAtIndex: i]; for (int j = 0; j < [ads count]; j++) { MediaInfo *ad = [ads objectAtIndex: j]; [self setDownloadComplete: ad]; // skip sending downloadFinish delegate if the media is marked as downloadDone if (!ad.downloadDone) { [delegate MediaDownloadDidFinish: ad.mediaId error: NO]; } ad.downloadDone = YES; } } downloadSegment = 0; } } // delegate back once all the main media segments have been downloaded. if (reqUrlCount == 0) { [self setDownloadComplete: mediaInfo]; state = DownloadState_Done; // skip sending downloadFinish delegate if the media is marked as downloadDone if (!bgDownloadMediaInfo.downloadDone) { [delegate MediaDownloadDidFinish: bgDownloadMediaInfo.mediaId error: NO]; } bgDownloadMediaInfo.downloadDone = YES; [urlArr release]; [pathArr release]; [finalUrlArr release]; [finalPathArr release]; // invalidate the NSURL session once complete [backgroundSession invalidateAndCancel]; } } - (void)URLSession: (NSURLSession *)session task: (NSURLSessionTask *)task didCompleteWithError: (NSError *)error { if (error) { NSLog(@"Failure to download request url (%@) with error (%@)", task.originalRequest.URL, error); } } - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { // save the total downloaded size [self downloaderDidReceiveData:bytesWritten]; // enable the log only for debugging purpose. // LogDebug(@"totalBytesExpectedToWrite %llu, totalBytesWritten %llu, %@", totalBytesExpectedToWrite, totalBytesWritten, downloadTask.currentRequest.URL); }
With out this code(beginBackgroundTaskWithExpirationHandler) the download stops when the app is pushed into background.
// AppDelegate_Phone.m - (void)applicationDidEnterBackground: (UIApplication *)application { NSLog(@"applicationDidEnterBackground"); UIApplication *app = [UIApplication sharedApplication]; UIBackgroundTaskIdentifier bgTask; bgTask = [app beginBackgroundTaskWithExpirationHandler:^{ [app endBackgroundTask:bgTask]; }]; }
-
And your question is...? – M. Prokhorov Nov 27 '17 at 17:05
-
As an aside, the instantiation of the background session doesn't really belong in `downloadWithURL:pathArr:mediaInfo:`. What if you try to download two files? You don't want two background sessions instantiated. Also, completely unrelated, it seems imprudent to use `[NSOperationQueue currentQueue]` for the delegate queue (as you don't know from which queue you called this). What if you happened to call this from some concurrent queue? It is probably safer to leave this `nil` and let it use its own serial queue for the background session's delegate calls. – Rob Nov 27 '17 at 17:51
-
It is a bit worrying that `didFinishDownloadingToURL` is referencing `finalUrlArr`. Are you storing this `finalUrlArr` in persistent storage and then reloading it everytime the app restarts? Remember that the app may have been terminated and restarted by the time the delegate methods are called. Also, you appear to be downloading one file at a time, but you'd often just go ahead and start all the downloads for the background session rather than having your app re-awakened at the end of each download to start the next download. That's more efficient (both re battery power and speed). – Rob Nov 27 '17 at 18:04
-
@prokhorov - Expected behavior: is that the download should continue when the app is pushed into background(press Home button) Actual : The download works fine as far the app is in the foreground but the download stops after press home button. I am able to pass the issue by using beginBackgroundTaskWithExpirationHandler but as per apple guideline we don't need it to support the download. – nsingh Nov 27 '17 at 18:26
-
@Rob - As you suggested I have tried setting the NSOperationQueue to nil. But that didn't help either. – nsingh Nov 27 '17 at 18:28
-
No, that is certainly not the source of the problem, but merely an aside. I'm more concerned about your implementation of `handleEventsForBackgroundURLSession` and `URLSessionDidFinishEventsForBackgroundURLSession`. If you haven't done that, when the download finishes, you won't be informed of the completion. The delegate queue (and other observations I made) are secondary to this basic "app restarted because download finished" process. – Rob Nov 27 '17 at 18:31
2 Answers
Have you implemented application:handleEventsForBackgroundURLSession:completionHandler:
in your app delegate? That should save the completion handler and start background session with the specified identifier.
If you don't implement that method, your app will not be informed if the download finishes after the app has been suspended (or subsequently terminated in the course of normal app lifecycle). As a result, it might look like the download didn't finish, even though it did.
(As an aside, note that if the user force-quits the app, that not only terminates the download, but obviously won't inform your app that the download was terminated until the user manually restarts the app at some later point and your app re-instantiates the background session. This is a second-order concern that you might not worry about until you get the main background processing working, but it's something to be aware of.)
Also, your URLSessionDidFinishEventsForBackgroundURLSession:
must call that saved completion handler (and dispatch this to the main queue).
Also, your design looks like it will issue only one request at a time. (I'd advise against that, but let's just assume it is as you've outlined above.) So, let's imagine that you have issued the first request and the app is suspended before it's done. Then, when the download is done, the app is restarted in the background and handleEventsForBackgroundURLSession
is called. Let's assume you fixed that to make sure it restarts the background session so that the various delegate methods can be called. Make sure that when you issue that second request for the second download that you use the existing background session, not instantiating a new one. You can have only one background session per identifier. Bottom line, the instantiation of the background session should be decoupled from downloadWithURL:pathArr:mediaInfo:
. Only instantiate a background session once.

- 415,655
- 72
- 787
- 1,044
Add "Required background modes" in your .plist
There, add the item "App downloads content from the network"

- 168
- 2
- 8