Episode #93

Background Transfers

15 minutes
Published on October 31, 2013

This video is only available to subscribers. Get access to this video and 572 others.

Background transfers are a new feature of iOS 7 where you can have the OS download files in the background and have them ready for your application when you launch it the next time. In this episode we write a simple image downloader that takes advantage of background sessions.


UPDATE:
A keen subscriber pointed out that there is a bug in the code presented in the video preventing the download from completing in the background. The fix was to create the background NSURLSession lazily so that it is not only created after the download button is pressed. I've updated the code on Github and the notes here. Thanks, @eallam!

Episode Links

Creating a background session

We lazily create it via the getter method so that it is created on demand.

- (NSURLSession *)session {
  if (!_session) {
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfiguration:@"your-unique-session-id-here"];
    _session = [NSURLSession sessionWithConfiguration:config
                                             delegate:self
                                        delegateQueue:nil];
  }
  return _session;
}

When using the background session configuration, you must use the session delegate methods, not the blocks.

Handling background transfer events

We have to start by listening on the app delegate to the event that lets us know the background transfer is about to be completed.

- (void)application:(UIApplication *)application
handleEventsForBackgroundURLSession:(NSString *)identifier
  completionHandler:(void (^)())completionHandler {

    NSDictionary *userInfo = @{@"sessionIdentifier": identifier,
                               @"completionHandler": completionHandler};

    [[NSNotificationCenter defaultCenter] postNotificationName:@"BackgroundSessionUpdated"
                                                        object:nil
                                                      userInfo:userInfo];
}

Here we just grab the session identifier that this download is for and the completion handler and raise a simple notification.

In our view controller we handle this notification...

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(handleBackgroundSession:)
                                                 name:@"BackgroundSessionUpdated"
                                               object:nil];
}

- (void)handleBackgroundSession:(NSNotification *)notification {
    if ([notification.userInfo[@"sessionIdentifier"] isEqualToString:self.session.configuration.identifier]) {
        self.backgroundCompletionHandler = notification.userInfo[@"completionHandler"];
    }
}

... and we simply save the completion block. Once the download has completed, it will call the normal delegate methods and end with a call to:

- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
    if (self.backgroundCompletionHandler) {
        self.backgroundCompletionHandler();
        self.backgroundCompletionHandler = nil;
    }
}