URL 应用在后台时请求超时
URL request times out when app is in the background
我有一个可以调用 API 请求的应用程序。一些用户遇到了一个错误,当他们在获取数据时关闭应用程序并稍后打开它时,该应用程序会抛出超时错误。
我正在使用标准的 URLSession 数据任务,如下例所示:
var session = URLSession(configuration: .ephemeral, delegate: self, delegateQueue: queue)
private func loadModels -> AnyPublisher<[Model], LoadModelsUseCaseError> {
guard let keyID = keyAdapter.getKeyID() else {
return Fail<[Model], LoadModelsUseCaseError>(error: .keyIDNotFound).eraseToAnyPublisher()
}
let url = Environment.loadModelsURL(for: keyID)
return apiAdapter.session
.dataTaskPublisher(for: url)
.decode(type: [Model].self, decoder: decoder)
.mapError(LoadModelsUseCaseError.init)
.eraseToAnyPublisher()
}
一种解决方法是在我调用该方法的视图模型中调用 .retry(1)
,但该解决方案有明显的缺陷。
另一种解决方法是捕获超时错误并再次调用加载方法。这也不完美,因为请求永远不会超时(即使在相关情况下)。
有什么处理这种情况的建议吗?非常感谢
好的,所以我已经解决了将以下代码放入我的 APIAdapter / APIManager 组件中的问题:
// MARK: - Configuration
private func configureNewSession() {
session?.invalidateAndCancel()
backgroundSession?.invalidateAndCancel()
let configuration = URLSessionConfiguration.default
configuration.isDiscretionary = true
configuration.sessionSendsLaunchEvents = true
session = URLSession(configuration: configuration, delegate: self, delegateQueue: queue)
let backgroundSessionConfiguration = URLSessionConfiguration.background(withIdentifier: "background")
backgroundSessionConfiguration.isDiscretionary = true
backgroundSessionConfiguration.sessionSendsLaunchEvents = true
backgroundSession = URLSession(configuration: backgroundSessionConfiguration, delegate: self, delegateQueue: queue)
}
private func subscribeToApplicationStateNotifications() {
NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)
.sink { _ in
self.moveTasksToForeground()
}
.store(in: &subscriptions)
NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)
.sink { _ in
self.moveTasksToBackground()
}
.store(in: &subscriptions)
}
// MARK: - App Lifecycle
/// The method currently doesn't move tasks in the background (as only download / upload tasks can be resumed),
/// but cancels them. Cancelled tasks doesn't produce errors, so they doesn't need to be catched in the View Models.
public func moveTasksToBackground() {
guard case .foreground = state else {
return
}
// Arguments in completion handlers are: data tasks, download tasks and upload tasks respectively.
session.getTasksWithCompletionHandler { dataTasks, _, _ in
for dataTask in dataTasks {
dataTask.suspend()
// NOTE: - Download tasks can produce resume data that can be resumed by standard url session in rhe
// foreground.
//
// Example:
//
// guard let downloadTask = downloadTask as? URLSessionDownloadTask else {
// continue
// }
// downloadTask.cancel(byProducingResumeData: { [self] resumeData in
// var downloadTask: URLSessionDownloadTask? = nil
// if let resumeData = resumeData {
// downloadTask = backgroundSession.downloadTask(withResumeData: resumeData)
// }
// downloadTask?.resume()
// })
}
}
state = .background
}
/// The method currently doesn't move tasks in the background (as only download / upload tasks can be resumed),
/// but cancels them. Cancelled tasks doesn't produce errors, so they doesn't need to be catched in the View Models.
public func moveTasksToForeground() {
guard case .background = state else {
return
}
// Arguments in completion handlers are: data tasks, download tasks and upload tasks respectively.
backgroundSession.getTasksWithCompletionHandler { dataTasks, _, _ in
for dataTask in dataTasks {
dataTask.suspend()
// NOTE: - Download tasks can produce resume data that can be resumed by standard url session in rhe
// foreground.
//
// Example:
//
// guard let downloadTask = downloadTask as? URLSessionDownloadTask else {
// continue
// }
// downloadTask.cancel(byProducingResumeData: { [self] resumeData in
// var downloadTask: URLSessionDownloadTask? = nil
// if let resumeData = resumeData {
// downloadTask = urlSession.downloadTask(withResumeData: resumeData)
// }
// downloadTask?.resume()
// })
}
}
state = .foreground
}
当您暂停数据任务时,会话不会产生错误,因此无需在视图模型/视图/用例/服务/您调用 API 调用的任何地方过滤取消.您所要做的就是在用户打开应用程序/进入屏幕时刷新远程数据。
我有一个可以调用 API 请求的应用程序。一些用户遇到了一个错误,当他们在获取数据时关闭应用程序并稍后打开它时,该应用程序会抛出超时错误。
我正在使用标准的 URLSession 数据任务,如下例所示:
var session = URLSession(configuration: .ephemeral, delegate: self, delegateQueue: queue)
private func loadModels -> AnyPublisher<[Model], LoadModelsUseCaseError> {
guard let keyID = keyAdapter.getKeyID() else {
return Fail<[Model], LoadModelsUseCaseError>(error: .keyIDNotFound).eraseToAnyPublisher()
}
let url = Environment.loadModelsURL(for: keyID)
return apiAdapter.session
.dataTaskPublisher(for: url)
.decode(type: [Model].self, decoder: decoder)
.mapError(LoadModelsUseCaseError.init)
.eraseToAnyPublisher()
}
一种解决方法是在我调用该方法的视图模型中调用 .retry(1)
,但该解决方案有明显的缺陷。
另一种解决方法是捕获超时错误并再次调用加载方法。这也不完美,因为请求永远不会超时(即使在相关情况下)。
有什么处理这种情况的建议吗?非常感谢
好的,所以我已经解决了将以下代码放入我的 APIAdapter / APIManager 组件中的问题:
// MARK: - Configuration
private func configureNewSession() {
session?.invalidateAndCancel()
backgroundSession?.invalidateAndCancel()
let configuration = URLSessionConfiguration.default
configuration.isDiscretionary = true
configuration.sessionSendsLaunchEvents = true
session = URLSession(configuration: configuration, delegate: self, delegateQueue: queue)
let backgroundSessionConfiguration = URLSessionConfiguration.background(withIdentifier: "background")
backgroundSessionConfiguration.isDiscretionary = true
backgroundSessionConfiguration.sessionSendsLaunchEvents = true
backgroundSession = URLSession(configuration: backgroundSessionConfiguration, delegate: self, delegateQueue: queue)
}
private func subscribeToApplicationStateNotifications() {
NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)
.sink { _ in
self.moveTasksToForeground()
}
.store(in: &subscriptions)
NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)
.sink { _ in
self.moveTasksToBackground()
}
.store(in: &subscriptions)
}
// MARK: - App Lifecycle
/// The method currently doesn't move tasks in the background (as only download / upload tasks can be resumed),
/// but cancels them. Cancelled tasks doesn't produce errors, so they doesn't need to be catched in the View Models.
public func moveTasksToBackground() {
guard case .foreground = state else {
return
}
// Arguments in completion handlers are: data tasks, download tasks and upload tasks respectively.
session.getTasksWithCompletionHandler { dataTasks, _, _ in
for dataTask in dataTasks {
dataTask.suspend()
// NOTE: - Download tasks can produce resume data that can be resumed by standard url session in rhe
// foreground.
//
// Example:
//
// guard let downloadTask = downloadTask as? URLSessionDownloadTask else {
// continue
// }
// downloadTask.cancel(byProducingResumeData: { [self] resumeData in
// var downloadTask: URLSessionDownloadTask? = nil
// if let resumeData = resumeData {
// downloadTask = backgroundSession.downloadTask(withResumeData: resumeData)
// }
// downloadTask?.resume()
// })
}
}
state = .background
}
/// The method currently doesn't move tasks in the background (as only download / upload tasks can be resumed),
/// but cancels them. Cancelled tasks doesn't produce errors, so they doesn't need to be catched in the View Models.
public func moveTasksToForeground() {
guard case .background = state else {
return
}
// Arguments in completion handlers are: data tasks, download tasks and upload tasks respectively.
backgroundSession.getTasksWithCompletionHandler { dataTasks, _, _ in
for dataTask in dataTasks {
dataTask.suspend()
// NOTE: - Download tasks can produce resume data that can be resumed by standard url session in rhe
// foreground.
//
// Example:
//
// guard let downloadTask = downloadTask as? URLSessionDownloadTask else {
// continue
// }
// downloadTask.cancel(byProducingResumeData: { [self] resumeData in
// var downloadTask: URLSessionDownloadTask? = nil
// if let resumeData = resumeData {
// downloadTask = urlSession.downloadTask(withResumeData: resumeData)
// }
// downloadTask?.resume()
// })
}
}
state = .foreground
}
当您暂停数据任务时,会话不会产生错误,因此无需在视图模型/视图/用例/服务/您调用 API 调用的任何地方过滤取消.您所要做的就是在用户打开应用程序/进入屏幕时刷新远程数据。