I'm trying to set up a download using NSURLSession
that will continue in the background.
I have a singleton class called DownloadManager
which builds the NSURLSession
and starts a download task like this:
- (id)init
{
self = [super init];
if (self) {
self.queue = [[NSOperationQueue alloc] init];
self.queue.maxConcurrentOperationCount = 1;
// Initialize the background session.
self.session = [self backgroundSession];
}
return self;
}
- (NSURLSession *)backgroundSession
{
static NSURLSession *session = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.mycompany.myapp.BackgroundSession"];
session = [NSURLSession sessionWithConfiguration:sessionConfiguration
delegate:self
delegateQueue:self.queue];
});
return session;
}
- (void)startDownload:(Download *)download
{
NSURL *remoteURL = ...
NSURLSessionDownloadTask *task = [self.session downloadTaskWithURL:remoteURL];
[task resume];
}
I've implemented the NSURLSessionDelegate
and NSURLSessionDownloadDelegate
methods including URLSessionDidFinishEventsForBackgroundURLSession:
. Additionally, my application delegate implements application:handleEventsForBackgroundURLSession:completionHandler:
.
However, when I move my application to the background by pressing the home button after starting a download, all download delegate methods stop firing and application:handleEventsForBackgroundURLSession:completionHandler:
is never called. The download continues, and if I wait long enough for it to finish then URLSession:downloadTask:didFinishDownloadingToURL:
is called the moment I bring my app back to the foreground. This means that I can't do any post-processing in the background (e.g. save to core data, start a new download, etc.).
I tried adding the 'Background fetch' background mode to my plist in case that was required, and I tried changing my the identifier used to create the NSURLSessionConfiguration
background configuration as suggested in this answer. Have I made a mistake in setting this up, or am I not supposed to be able to handle download delegate events in the background?
You won't see download delegate events called while the individual downloads finish in the background, but rather only after the app is restarted. e.g., when all of the downloads are successfully finished (when handleEventsForBackgroundURLSession
is called) or when you manually restart the app.
As to why you're not seeing handleEventsForBackgroundURLSession
called, I can only think of a couple (admittedly, unlikely) possibilities:
Make sure that the signature of the handleEventsForBackgroundURLSession
in your app delegate is absolutely correct (capitalization, spelling, etc.). A minor typo will not generate any warning, but it will result in its not getting called. So it should be:
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler
{
// save the completionHandler here
}
Are you sure that all of your background downloads are finishing? If one of them hangs or fails for some reason, that will prevent the completion of the background session to be triggered, and because the delegate method is only called when everything is done, that means that your app will not be activated. I recommend carefully checking that all of the downloads have finished.
Have you checked the device's console? (You can see this if you go to the "Devices" section of the Xcode "Organizer".) Sometimes there are interesting diagnostic error messages which the background daemon will log for you. Check that out if you haven't already. Lots of interesting stuff there.
If you manually kill the app via SpringBoard (double tapping on physical home button, hold down app icon until its jiggling, click on red "x"), that will kill the background download tasks, and therefore you won't get notification that all of the downloads are done. Make sure you implement URLSession:task:didCompleteWithError:
so you can see this error (and any others).
If, however, I programmatically crash the app as illustrated in the WWDC 2013 video What’s New in Foundation Networking, or if it is terminated by the system, then the background downloads finish correctly and the app delegate method is getting called correctly.
Just a few ideas to consider.
I had a similar issue, the application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void)
in my AppDelegate never was called when the app was downloading content in the background.
Turned out that I set the sharedContainerIdentifier
in the URLSessionConfiguration
.
After I removed this line out of the URLSessionConfiguration
it all started to work.
sessionConfiguration.sharedContainerIdentifier = "my.shared.group"