20

Is there really no way to run an UPLOAD task while an iOS app is in the background? This is ridiculous. Been looking at various stuff like NSURLSessionUploadTask, dispatch_after and even NSTimer, but nothing works for more than the meager 10 seconds the app lives after being put in the background.

How do other apps that have uploads work? Say, uploading an image to Facebook and putting the app in the background, will that cancel the upload?

Why cannot iOS have background services or agents like Android and Windows Phone has?

This is a critical feature of my app, and on the other platforms is works perfectly.

Any help is appreciated :(

Rob
  • 415,655
  • 72
  • 787
  • 1,044
Christoffer
  • 545
  • 1
  • 5
  • 19
  • This seems to be possible using server side programming if you are able to create and call a webservice to do task for you. Check the link: http://stackoverflow.com/questions/25725811/upload-image-video-to-facebook-asynchronously-using-php-sdk-4 . Not sure, if it is relevant to you in anyway. – Piyush Arora Sep 25 '14 at 06:47

2 Answers2

31

You can continue uploads in the background with a “background session”. The basic process of creating a background URLSessionConfiguration with background(withIdentifier:) is outlined in Downloading Files in the Background. That document focuses on downloads, but the same basic process works for upload tasks, too.

Note:

  • you have to use the delegate-based URLSession;

  • you cannot use the completion handler renditions of the task factory methods with background sessions;

  • you also have to use uploadTask(with:fromFile:) method, not the Data rendition ... if you attempt to use uploadTask(with:from:), which uses Data for the payload, with background URLSession you will receive exception with a message that says, “Upload tasks from NSData are not supported in background sessions”; and

  • your app delegate must implement application(_:handleEventsForBackgroundURLSession:completionHandler:) and capture that completion handler which you can then call in your URLSessionDelegate method urlSessionDidFinishEvents(forBackgroundURLSession:) (or whenever you are done processing the response).


By the way, if you don't want to use background NSURLSession, but you want to continue running a finite-length task for more than a few seconds after the app leaves background, you can request more time with UIApplication method beginBackgroundTask. That will give you a little time (formerly 3 minutes, only 30 seconds in iOS 13 and later) complete any tasks you are working on even if the user leave the app.

See Extending Your App's Background Execution Time. Their code snippet is a bit out of date, but a contemporary rendition might look like:

func initiateBackgroundRequest(with data: Data) {
    var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid

    // Request the task assertion and save the ID.
    backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "Finish Network Tasks") {
        // End the task if time expires.
        if backgroundTaskID != .invalid {
            UIApplication.shared.endBackgroundTask(backgroundTaskID)
            backgroundTaskID = .invalid
        }
    }

    // Send the data asynchronously.
    performNetworkRequest(with: data) { result in
        // End the task assertion.
        if backgroundTaskID != .invalid {
            UIApplication.shared.endBackgroundTask(backgroundTaskID)
            backgroundTaskID = .invalid
        }
    }
}

Please don’t get lost in the details here. Focus on the basic pattern:

  • begin the background task;
  • supply a timeout clause that cleans up the background task if you happen to run out of time;
  • initiate whatever you need to continue even if the user leaves the app; and
  • in the completion handler of the network request, end the background task.
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Are you sure regarding the `completionHandler`? I didn't find any mention in the `uploadTaskWithRequest:fromFile:completionHandler:` method documentation saying that it can't be used for background upload task. – tonymontana Jun 02 '17 at 20:26
  • See the [documentation for `URLSession`](https://developer.apple.com/reference/foundation/urlsession) which says "Because your app might quit and be relaunched while the transfer is in progress, completion handler blocks are not supported." – Rob Jun 02 '17 at 20:30
  • Referring to the notes on the `background` class method, are they still applicable or is there any update? – Ahmad F Jul 31 '18 at 08:52
  • Hi, Thanks for the info, is the default uploadTask uses multipart execution? Or if not can I make my multipart form request to be background executable? – Amber K Feb 12 '19 at 10:33
  • @AmberK - `uploadTask` just sends the file as is. So, if you want to send `multipart/form-data`, you have to (a) manually construct the contents of the file to be structured as `multipart/form-data` (much like you have to manually construct the `httpBody` of a `dataTask`); and (b) manually set the `Content-Type` header of the `URLRequest`. – Rob Feb 12 '19 at 15:06
  • Hi @Rob , thanks for pointing me in the right direction. However I don't find much information on how to implement it on my own, I guess I have to rely on Alamofire for that, which I don't want but will save time. Lets say I formed the multipart request, will it be able to run in background if select `URLSessionConfiguration` as `.background`? Would I need anything else as well.. – Amber K Feb 13 '19 at 11:58
  • @AmberK - That’s the basic idea. Unfortunately, they’re using an input stream, but for background sessions you need to explicitly save it to a file first, and then supply the file to the upload task. – Rob Feb 13 '19 at 14:53
  • Where is this said (documentation ref)? "you also have to use uploadTask(with:fromFile:) method, not the Data rendition." – hannojg Feb 24 '21 at 19:25
  • 1
    @hannojg - It was once outlined in their old “URL Programming Guide”, which they have subsequently replaced with new documentation that fails to clearly outline the background upload requirements. And the original pre-2015 videos that described this appear be gone, too. But just try `uploadTask(with:from:)` with background session and you will receive an unambiguous exception that reports, “Upload tasks from NSData are not supported in background sessions.” That confirms that these constraints on background uploads still apply. – Rob Feb 24 '21 at 21:27
  • 1
    FWIW, this “NSData are not supported” for background uploads is a perfectly understandable limitation, IMHO. They don't really want to have their background daemon to have to keep huge `Data`/`NSData` objects in memory. If a resource is so large that you need background uploads (rather than just requesting an extra 30 seconds to complete standard `URLSession` after user leaves the app), then a file-based upload is going to be preferred, anyway (even for non-background `URLSession`). – Rob Feb 24 '21 at 21:33
  • Is a background mode entitlement also required? – Curiosity Nov 22 '21 at 07:45
  • Neither for background `URLSession` nor background task. – Rob Nov 22 '21 at 10:06
0
class ViewController: UIViewController, URLSessionTaskDelegate {
override func viewDidLoad() {
    super.viewDidLoad()
    
    let url = URL(string: "http://0.0.0.0")!
    let data = "Secret Message".data(using: .utf8)!
    
    let tempDir = FileManager.default.temporaryDirectory
    let localURL = tempDir.appendingPathComponent("throwaway")
    try? data.write(to: localURL)
    
    let request = URLRequest(url: url)
    let config = URLSessionConfiguration.background(withIdentifier: "uniqueId")
    let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
    let task = session.uploadTask(with: request, fromFile: localURL)
    task.resume()
}

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
    print("We're done here")
}