Swift 后台执行
Swift Background Execution
我在 AWS 上有一个由 HTTP Post 请求触发的步骤函数。该功能可能需要几秒钟才能完成。如果用户将应用程序置于后台,我希望继续执行,并在用户将应用程序放回前台(如果执行已完成)后正确导航到下一个屏幕。
我的 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 块可以吗?我应该 post .receive(on: )
到 DispatchQueue.global 吗?
您根本不必将此后台任务分派到后台队列。 (不要将指代应用程序状态的“后台任务”与控制使用哪些线程的“后台队列”混为一谈。)
此外,正如 documentation 所说,过期处理程序闭包在主线程上运行:
The system calls the handler synchronously on the main thread, blocking the app’s suspension momentarily.
所以你真的想在主线程上保留与 backgroundTaskID
的所有交互,否则你将不得不实现一些其他的同步机制。
作为一个好的做法,请确保在完成异步请求后结束后台任务(而不是依赖 expiration/timeout 闭包)。
我在 AWS 上有一个由 HTTP Post 请求触发的步骤函数。该功能可能需要几秒钟才能完成。如果用户将应用程序置于后台,我希望继续执行,并在用户将应用程序放回前台(如果执行已完成)后正确导航到下一个屏幕。
我的 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 块可以吗?我应该 post .receive(on: )
到 DispatchQueue.global 吗?
您根本不必将此后台任务分派到后台队列。 (不要将指代应用程序状态的“后台任务”与控制使用哪些线程的“后台队列”混为一谈。)
此外,正如 documentation 所说,过期处理程序闭包在主线程上运行:
The system calls the handler synchronously on the main thread, blocking the app’s suspension momentarily.
所以你真的想在主线程上保留与 backgroundTaskID
的所有交互,否则你将不得不实现一些其他的同步机制。
作为一个好的做法,请确保在完成异步请求后结束后台任务(而不是依赖 expiration/timeout 闭包)。