Question

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?

Was it helpful?

Solution

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:

  1. 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
    }
    
  2. 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.

  3. 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.

  4. 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.

OTHER TIPS

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"
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top