Active Filters: Multitasking

Imagine an app that downloads and plays movies. If a user begins downloading a movie, it'd be great if the file could continue downloading even if the user presses the home button and sends the app to the background. NSURLSessionDownloadTask can help. Let's take a look.

First we create a new background configuration and session. Then we use that session to create a new download task:

class ViewController: UIViewController, NSURLSessionDownloadDelegate, NSURLSessionTaskDelegate {
    func downloadMovieFile() {
    let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("downloads-session")
    let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)

    let task = session.downloadTaskWithURL(NSURL(string: "http://supersecret.disney.com/movies/episodeVII.mp4")!)
    task.resume()
  }
}

We can grab the downloaded data pretty easily, by implementing this function from NSURLSessionDownloadDelegate:

func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
    print("Movie data written to \(location)")
}

Last but not least, we'll need to implement a function from NSURLSessionTaskDelegate. In it we'll call finishTasksAndInvalidate on the session. This ensures everything is cleaned up and the session lets go of us (self) as its delegate.

func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
  if let error = error { print("Download Error: \(error)") }
  session.finishTasksAndInvalidate()
}

Topics

#76: Background Fetch 🔄

Topics

It's very common for an app to need to regularly check for new data. Background Fetch allows us to do just that, while running in the background.

The system will periodically wake our app up and ask it to fetch new data. Once awoken, we'll be able to run whatever code we need to check for or fetch new data.

Lastly, we'll need to tell the system what the result of our fetch was. The system will use our response to decide how frequently our app needs to fetch.

Let's start by going to the capabilities tab of our project, and turning on Background Modes. Then we'll check the box next to the Background fetch mode.

Next, the code. We'll set a minimum background fetch interval in our didFinishLaunching function.

The performFetchWithCompletionHandler function is the last piece of the puzzle. We fetch any new data that might exist, and call the provided completionHandler with an enum describing the result.

func application(application: UIApplication,
  didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
  application.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
  return true
}

func application(application: UIApplication, 
  performFetchWithCompletionHandler completionHandler: ((UIBackgroundFetchResult) -> Void)) {

  JEDIGossipAPI.fetchStories() { (stories: [JEDIGossipStory], error: NSError?) in
    guard stories.count > 0 else { completionHandler(.NoData); return }
    guard error == nil else { completionHandler(.Failed); return }

    completionHandler(.NewData)
  }
}

Calling the completion handler will also trigger the system to take a new snapshot of our app for the multitasking interface, so we should make sure we've updated any views that need it beforehand.

To test everything, we can use the Debug > Simulate Background Fetch command in the Simulator, or create a background fetch-specific scheme like we covered in Bite #65.