Instruments Network: Background URLSession instance appears not to complete

As stated in the title.

I am running the following code. Each time I perform an API call, I create a new instance of URLSession and use a background-configured session to allow background API calls. ` Code being executed:

import Foundation

// Model definitions struct RandomUserResponse: Codable { let results: [RandomUser] }

struct RandomUser: Codable { let name: Name let email: String }

struct Name: Codable { let first: String let last: String }

// Fetcher class class RandomUserFetcher: NSObject, URLSessionDataDelegate { private var receivedData = Data() private var completion: ((RandomUser?) -> Void)? private var session: URLSession!

func fetchRandomUserInBackground(completion: @escaping (RandomUser?) -> Void) {
    self.completion = completion

    let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.randomuser.bg")
    session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
    let url = URL(string: "https://randomuser.me/api/" )!
    let task = session.dataTask(with: url)
    task.resume()
}

// Data received
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
    receivedData.append(data)
}

// Completion
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
    defer { self.session.finishTasksAndInvalidate() }
    guard error == nil else {
        print("Error: \(error!)")
        completion?(nil)
        return
    }
    do {
        let response = try JSONDecoder().decode(RandomUserResponse.self, from: receivedData)
        completion?(response.results.first)
    } catch {
        print("Decoding error: \(error)")
        completion?(nil)
    }
}

}`

Called in viewDidLoad, etc.:

let fetcher = RandomUserFetcher()
fetcher.fetchRandomUserInBackground { user in
    if let user = user {
        print("Name: \(user.name.first) \(user.name.last), Email: \(user.email)")
    } else {
        print("Failed to fetch random user.")
    }
}

In Instruments' Network instrument, I focus on my app's process, use 'Command + 3', and switch to 'List: URLSessionTasks'.

Even though didCompleteWithError is called and the API call fully completes, the Duration keeps increasing, and the Success column remains '-' (neither 'Yes' nor 'No').

For non-background URLSessions, the session shows up as 'unnamed session', but for background URLSessions, it appears as 'unnamed background session 1 (XXXXXX-XXXXXX-XXXXX)'.

Does this mean the session is not actually being completed? I've checked Debug Memory Graph and confirmed there is no NSURLSession memory leak, but is it possible that the app is somehow still retaining session information internally?

I also suspect that Instruments may not be able to fully track background URLSession tasks.

Answered by DTS Engineer in 841710022
Each time I perform an API call, I create a new instance of URLSession

That’s not good. Session objects are fairly heavyweight, and you should not be creating a session per network request. Background sessions objects are significantly heavier than standard session objects, and creating a background session per request is seriously suboptimal.

I recommend that you change your code to share the session between all similar network requests. In many cases you can get away with having a single session object that lives for the entire lifetime of your app.

Share and Enjoy

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

Accepted Answer

I forgot to mention my operating environment.

13-inch, M1, 2020 macOS 15.5 (24F74) Xcode 16.2

Each time I perform an API call, I create a new instance of URLSession

That’s not good. Session objects are fairly heavyweight, and you should not be creating a session per network request. Background sessions objects are significantly heavier than standard session objects, and creating a background session per request is seriously suboptimal.

I recommend that you change your code to share the session between all similar network requests. In many cases you can get away with having a single session object that lives for the entire lifetime of your app.

Share and Enjoy

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

What I really wanted to ask is the following: When communicating using a background URLSession and calling session.finishTasksAndInvalidate() after the communication is complete, the “Success” status for the corresponding URLSession in Instruments’ Network tab does not become either “Yes” or “No”.

In this situation, since the communication is complete, can we say that the session on the device side is fully closed?

This phenomenon occurs regardless of whether multiple instances are created; it also happens even when communication is performed only once after launching the app.

Instruments Network: Background URLSession instance appears not to complete
 
 
Q