【问题标题】:Swift Background Execution快速后台执行
【发布时间】:2022-01-15 08:16:46
【问题描述】:

我在 AWS 上有一个由 HTTP Post 请求触发的 step 函数。该功能可能需要几秒钟才能完成。如果用户将应用程序置于后台,我希望继续执行,并在用户将应用程序放回前台后正确导航到下一个屏幕(如果执行已完成)。

我的 API 客户端端点如下所示:

 func connect<OutputType: Decodable>(to request: URLRequestConvertible, decoder: JSONDecoder) -> AnyPublisher<Result<OutputType, Error>, Never> {
    var request = request.asURLRequest()
    
    if let token: String = KeychainWrapper.standard.string(forKey: "apiToken") {
        request.addValue(token, forHTTPHeaderField: "Authorization")
    }
    
    let configuration = URLSessionConfiguration.default
    configuration.waitsForConnectivity = true
    let session = URLSession(configuration: configuration)
    
    return session.dataTaskPublisher(for: request)
        .tryMap({ (data, response) -> Data in
            guard let response = response as? HTTPURLResponse else { throw NetworkError.invalidResponse }
            guard 200..<300 ~= response.statusCode else {
                throw NetworkError.invalidStatusCode(statusCode: response.statusCode)
                
            }
            return data
        })
        .decode(type: OutputType.self, decoder: decoder)
        .map(Result.success)
        .catch { error -> Just<Result<OutputType, Error>> in Just(.failure(error)) }
        .receive(on: DispatchQueue.main)
        .eraseToAnyPublisher()
}

我想知道实现此调用的最佳做法。我目前正在使用下面的 beginBackgroundTask。

func makeRequest() {
    DispatchQueue.global(qos: .userInitiated).async {
        self.backgroundTaskID = UIApplication.shared.beginBackgroundTask (withName: "Request Name") {
            UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
            self.backgroundTaskID = .invalid
        }
        <implementation>
    }
}

但是,implementation 仅在我嵌套了 DispatchQueue.main.async 块时才有效,在这些块中我在发出 HTTP 请求后执行更多逻辑(例如在收到响应后确定要导航到下一个屏幕。

这是最好的方法吗?在 DispatchQueue.global 块内可以有几个不同的嵌套 DispatchQueue.main.async 块吗?我应该将.receive(on: ) 发布到 DispatchQueue.global 吗?

【问题讨论】:

    标签: ios swift urlsession uibackgroundtask backgroundtaskidentifier


    【解决方案1】:

    您根本不必将此后台任务分派到后台队列。 (不要将指应用程序状态的“后台任务”与控制使用哪些线程的“后台队列”混为一谈。)

    此外,正如documentation 所说,到期处理程序闭包在主线程上运行:

    系统在主线程上同步调用处理程序,暂时阻塞应用的暂停。

    因此,无论如何,您确实希望在主线程上保留与backgroundTaskID 的所有交互,否则您将不得不实现一些其他同步机制。


    作为一种良好做法,请确保在异步请求完成时结束后台任务(而不是依赖到期/超时关闭)。

    【讨论】:

    • 通过“在主线程上同步调用处理程序”:是过期处理程序还是与任务关联的实际块?即是mainQueue.sync(item: expirationhandlerItem) 还是mainQueue.sync(item: theactulTaskItem)
    • 到期处理程序闭包被分派到主队列。但它不会在持续时间内阻塞主线程,而是文档告诉您,当您的后台任务用完时间时,它会同步调用完成处理程序。所以,只要让你的后台任务做它需要的任何事情,就像平常一样,遵循标准、良好实践、异步模式,你会没事的。只是不要阻塞主线程(无论如何你永远不会这样做)。
    • 明白了。我想它是sync,因为如果它是async,那么expirationHandler 有可能被调用得太晚,即在应用程序暂停之后。如果你打电话给endBackgroundTask 然后过期处理程序将被取消,这是否正确?即会调用与expirationHandlerWorkItem.cancel()相似的东西。
    • 如果你结束后台任务,你的过期处理程序将不会被调用。因此,在这种情况下,它类似于取消一个工作项,尽管实现的细节并不真正相关。
    猜你喜欢
    • 1970-01-01
    • 2016-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多