Build Status Platforms Documentation Discord

背景

后台任务和网络

集成

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

概念

URLSession 的后台上传和下载功能入门相对简单。但是,管理它们却出奇的困难。核心挑战在于操作可能在你的进程甚至没有运行的情况下启动和/或完成。你不能仅仅等待一个完成处理程序或 await 调用。这通常意味着你必须涉及持久性存储,以便在进程启动之间协调状态。

你通常还需要使用系统提供的 API 将你的会话重新连接到启动之间发生的任何工作。这可以通过几种不同的方式完成,具体取决于你的项目类型以及你希望系统如何工作。

因为涉及到持久状态,并且网络操作可能已经在进行中,所以 UploaderDownloader 类型支持重启操作。你的工作是跟踪正在进行的工作。然后,你可以使用 UploaderDownloader 类型重新启动启动时尚未完成的任何工作。它们将负责确定操作是需要实际启动还是仅重新连接到现有工作。

用法

上传

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
    }
}

下载

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 支持

如果你在 widget 中使用 Downloader,你必须在你的 WidgetConfiguration 中重新连接会话。 这是方法:

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)
        }
    }
}

更复杂的使用

这个包用于管理 Wells(一个诊断报告提交系统)的后台上传功能。你可以查看该项目,了解如何管理后台上传的更复杂示例。

贡献和协作

我很乐意听到你的声音! Issues、Discussions 或 pull requests 都很好。 Discord server 也可用于实时帮助,但我强烈倾向于以文档的形式回答。

我更喜欢协作,并且很乐意找到合作方式,如果你有类似的项目。

我更喜欢用制表符进行缩进以提高可访问性。但是,我宁愿你使用你想要的系统并创建一个 PR,也不愿因为空格而犹豫。

通过参与本项目,你同意遵守 贡献者行为准则