4

I want to make an additional HTTP request after background download/upload in order to confirm that the application finished downloading/uploading. Let me show you a simple example.

First we need to create download/upload task.

let configuration = URLSessionConfiguration.background(withIdentifier: UUID().uuidString)
configuration.sessionSendsLaunchEvents = true
configuration.isDiscretionary = true
let session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
session.downloadTask(with: largeFileURL).resume()

Then we need to fire some additional request after download/upload finishes. In order to prevent application from being suspended I'm using background task.

func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {

    backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: { [weak self] in
        finishBackgroundTask()
    })

    let task = URLSession.shared.dataTask(with: someURL) { data, response, error in
        // Process response.
        finishBackgroundTask()
    }
    task.resume()    
}

private func finishBackgroundTask() {
    UIApplication.shared.endBackgroundTask(backgroundTaskIdentifier)
    backgroundTaskIdentifier = .invalid
}

The last thing is to implement application delegate method:

func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {

}

Question

Is it a proper way to make some work after background transfer?

Nominalista
  • 4,632
  • 11
  • 43
  • 102

2 Answers2

3

The best approach, if memory serves, is to start the new request before you call the completion block. Be aware, however, that no matter how you do it, if you repeatedly make short requests, the OS will rapidly increase the delay between when a background download finishes and when your app gets relaunched in the background to handle the session events.

dgatwood
  • 10,129
  • 1
  • 28
  • 49
  • Thank you for the answer. Should I call completion block from handleEventsForBackgroundURLSession after the request completes or just after resuming the request? Should I wrap this request into background task? – Nominalista Dec 27 '18 at 22:07
  • After calling `resume` on the new task. You don't want to keep the OS waiting very long — just long enough to schedule things and do whatever administrative work you have to do — or else again the OS will start penalizing you on scheduling. – dgatwood Dec 29 '18 at 03:13
2

I propose to create a completionHandler into your AppDelegate

var backgroundSessionCompletionHandler: (() -> Void)?

Then in the handleEventsForBackgroundURLSession UIApplicationDelegate's method you define you completion handler

func application(_ application: UIApplication, handleEventsForBackgroundURLSession 
identifier: String, completionHandler: @escaping () -> Void) {
    backgroundSessionCompletionHandler = {
        // Execute your additional HTTP request
    }
}

The last step is to call this completion handler when the download is finished

func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
    if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
        if let completionHandler = appDelegate.backgroundSessionCompletionHandler {
            appDelegate.backgroundSessionCompletionHandler = nil
            DispatchQueue.main.async(execute: {
                completionHandler()
            })
        }
    }
}

I hope this helps.

Mourad Brahim
  • 531
  • 2
  • 15