Background

main

Background Tasks and Networking
ChimeHQ/Background

Build Status Platforms Documentation Matrix

Background

Background Tasks and Networking

Integration

dependencies: [
    .package(url: "https://github.com/ChimeHQ/Background", branch: "main")
]

Concept

URLSession's background upload and download facilities are relatively straightforward to get started with. But, they are surprisingly difficult to manage. The core challenge is an operation could start and/or complete while your process isn't even running. You cannot just wait for a completion handler or await call. This usually means you have to involve persistent storage to juggle state across process launches.

You also typically need to make use of system-provided API to reconnect your session to any work that has happened between launches. This can be done a few different ways, depending on your type of project and how you'd like your system to work.

Because persistent state is involved and networking operations might already be in-progress, the Uploader and Downloader types support restarting operations. Your job is to track that in-flight work. You can then just restart any work that hasn't yet completed on launch using the Uploader or Downloader types. They will take care of determining if the operations need to be actually started or just reconnected to existing work.

Usage

Uploading

import Foundation
import Background

let config = URLSessionConfiguration.background(withIdentifier: "com.my.background-id")
let uploader = Uploader(sessionConfiguration: config)

let request = URLRequest(url: URL(string: "https://myurl")!)
let url = URL(fileURLWithPath: "path/to/file")
let identifier = "some-stable-id-appropriate-for-the-file"

Task<Void, Never> {
    // remember, this await may not return during the processes entire lifecycle!
    let response = await uploader.networkResponse(from: request, uploading: url, with: identifier)
    
    switch response {
    case .rejected:
        // the server did not like the request
        break
    case let .retry(urlResponse):
        let interval = urlResponse.retryAfterInterval ?? 60.0
        
        // transient failure, could retry with interval if that makes sense
        break
    case let .failed(error):
        // failed and a retry is unlikely to succeed
        break
    case let .success(urlResponse):
        // upload completed successfully
        break
    }
}

Downloading

import Foundation
import Background

let config = URLSessionConfiguration.background(withIdentifier: "com.my.background-id")
let downloader = Downloader(sessionConfiguration: config)

let request = URLRequest(url: URL(string: "https://myurl")!)
let identifier = "some-stable-id-appropriate-for-the-file"

Task<Void, Never> {
    // remember, this await may not return during the processes entire lifecycle!
    let response = await downloader.networkResponse(from: request, with: identifier)

    switch response {
    case .rejected:
        // the server did not like the request
        break
    case let .retry(urlResponse):
        let interval = urlResponse.retryAfterInterval ?? 60.0

        // transient failure, could retry with interval if that makes sense
        break
    case let .failed(error):
        // failed and a retry is unlikely to succeed
        break
    case let .success(url, urlResponse):
        // download completed successfully at url
        break
    }
}

Widget Support

If you are making use of Downloader in a widget, you must reconnect the session as part of your WidgetConfiguration. Here's how:

struct YourWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: provider) { entry in
           YourWidgetView()
        }
        .onBackgroundURLSessionEvents { identifier, completion in
            // find/create your downloader instance using the system-supplied
            // identifier
            let downloader = lookupDownloader(with: identifier)
            
            // and allow it to handle the events, possibly resulting in
            // callbacks and/or async functions completing
            downloader.handleBackgroundSessionEvents(completion)
        }
    }
}

Background Tasks

It's disappointing that the BackgroundTasks framework isn't available for macOS. The library includes some preliminary work to build a nearly source-compatible version of BGTaskScheduler that works across all platforms. This replacement also permits unconditional use in the simulator, which makes it more convenient. Actual background work will still only take place on real devices.

The macOS implementation is build around NSBackgroundActivityScheduler, which works very differently internally.

As of right now, these types aren't public because the work isn't complete.

More Complex Usage

This package is used to manage the background uploading facilities of Wells, a diagnostics report submission system. You can check out that project for a much more complex example of how to manage background uploads.

Contributing and Collaboration

I would love to hear from you! Issues or pull requests work great. Both a Matrix space and Discord are available for live help, but I have a strong bias towards answering in the form of documentation. You can also find me on the web.

I prefer collaboration, and would love to find ways to work together if you have a similar project.

I prefer indentation with tabs for improved accessibility. But, I'd rather you use the system you want and make a PR than hesitate because of whitespace.

By participating in this project you agree to abide by the Contributor Code of Conduct.

Description

  • Swift Tools 6.0.0
View More Packages from this Author

Dependencies

  • None
Last updated: Mon May 19 2025 01:39:35 GMT-0900 (Hawaii-Aleutian Daylight Time)