concurrent downloading of files with URLSession downloadTask with background configuration.

According to documentation, the URLSession background tasks continue even when the app is suspended. What is the lifespan of the URLSessionDownloadDelegate object when app is suspended or terminated?

Will it get re-created and re-initialize properties when the app re-launches, or will it somehow restore the existing property values?

Also, urlSessionDidFinishEvents not getting called, and what do we need to do there with the backgroundCompletionHandler? Any insights are much appreciated. We are getting ready to launch and this is a roadblock. (visionOS26.4) Thank you.

@Observable
class DownloadManager: NSObject,  URLSessionDownloadDelegate {
...


let config = URLSessionConfiguration.background(withIdentifier: "TestDL")
            config.sessionSendsLaunchEvents = true

var urlSession = URLSession(configuration: config, delegate: self, delegateQueue: nil)


func downloadFiles(...
{
   // initiate multiple file downloads concurrently
   for url in urlList {
       let task = urlSession.downloadTask(with: url)
       task.resume()
   }
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
...

func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
...

func urlSession(_: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
...

// Not getting called ??
// Is this only called when app is suspended/terminated?
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
        print("didFinishEvents")

        Task { @MainActor in
        //urlSession?.finishTasksAndInvalidate()
        //urlSession = nil

        // not sure what to do here:
            if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
               let completionHandler = appDelegate.backgroundCompletionHandler {
                completionHandler()
                appDelegate.backgroundCompletionHandler = nil
            }
        }
}

Answered by DTS Engineer in 886718022

The below applies to iOS and all its child platforms, including visionOS. On macOS, as is so often the case, the store is subtly different (-:

What is the lifespan of the URLSessionDownloadDelegate object when app is suspended or terminated?

Once an app is suspended, one of two things can happen:

  • It get resumed.
  • It gets terminated.

In the first case, the app’s memory is preserved and thus any URLSession objects you created continue to exist. And the URLSession retains its delegate [1], so your delegate objects continue to exist.

If the app gets terminated then all of its memory goes away without any app code ever running again. The next time the app is launched — which might due to user activity or due to the system relaunching it in the background, for example, because your downloads have completed — it has to re-create any background sessions it was using. As part of this it has to create new delegate objects.

I generally recommend that you use a very small number of background sessions, in many apps it’s just one, and immediately create a URLSession object for each background session on app launch.

I recommend you have a read of Downloading files from websites. It covers the basics pretty well. I also have a forums post, Testing Background Session Code, that you’ll find useful. And Networking Resources has links to a bunch of other stuff.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Until its invalidated.

The below applies to iOS and all its child platforms, including visionOS. On macOS, as is so often the case, the store is subtly different (-:

What is the lifespan of the URLSessionDownloadDelegate object when app is suspended or terminated?

Once an app is suspended, one of two things can happen:

  • It get resumed.
  • It gets terminated.

In the first case, the app’s memory is preserved and thus any URLSession objects you created continue to exist. And the URLSession retains its delegate [1], so your delegate objects continue to exist.

If the app gets terminated then all of its memory goes away without any app code ever running again. The next time the app is launched — which might due to user activity or due to the system relaunching it in the background, for example, because your downloads have completed — it has to re-create any background sessions it was using. As part of this it has to create new delegate objects.

I generally recommend that you use a very small number of background sessions, in many apps it’s just one, and immediately create a URLSession object for each background session on app launch.

I recommend you have a read of Downloading files from websites. It covers the basics pretty well. I also have a forums post, Testing Background Session Code, that you’ll find useful. And Networking Resources has links to a bunch of other stuff.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Until its invalidated.

Hello Apple Engineer,

Thank you for all this detailed information! I’ve seen the first resource, but will be sure to dig into the other two.

I will need to use a few background sessions in the app. Are session delegate methods thread safe when running multiple downloadTasks on it? My attempt to make the session delegate writes thread safe has prevented it from moving the downloaded files. The same error is seen on the Sim and the Device. I’ve searched the forum posts and haven’t found anything related to this error.

I cannot post the full sample code here since it contains sensitive backend server information we don’t want to expose, but if you could take a look at DTS Case-ID: 19714751 containing the attached sample code I just updated, I would appreciate that very much.

I’ve also added the error message I’m seeing as a comment in the sample code. It is a very small test project, I think it will take you at most 2 minutes to read through it all :)

Thanks, bvsdev

Are session delegate methods thread safe when running multiple downloadTasks on it?

A session always calls delegate methods from the context of the operation queue that you supply when you create the session. You can change that queue to suit your needs:

  • If you want all the delegate callbacks for a specific session to be serialised, pass in a different serial queue for each session.
  • If you want all delegate callbacks across all sessions to be serialised, create a single serial queue and use that for all your sessions.

A good serial queue to use is .main. If you don’t want to use the main queue, create a serial queue like so:

let q = OperationQueue()
q.maxConcurrentOperationCount = 1

IMPORTANT If you don’t set maxConcurrentOperationCount, you get a concurrent queue, which will can be very confusing in this context.

One challenge here is that these serialisation isn’t visible to the Swift compile, so you need to tell it what’s what. Consider this delegate:

final class MySessionDelegate: NSObject, URLSessionDownloadDelegate {

    @MainActor var counter = 0

    nonisolated func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        counter += 1
     // ^ Main actor-isolated property 'counter' can not be mutated from a nonisolated context
        MainActor.assumeIsolated {
            counter += 1
        }
    }
}

It uses assumeIsolated(…) to tell the compiler that the non-isolated method is always called on the main actor. This pairs well with a session that uses the main queue, created like so:

let config: URLSessionConfiguration = …
let delegate = MySessionDelegate()
let session = URLSession(configuration: config, delegate: delegate, delegateQueue: .main)

If you want to run on a different queue things get more complex. Lemme know if that’s the case and I’ll explain your options.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

concurrent downloading of files with URLSession downloadTask with background configuration.
 
 
Q