应用程序在后台时 URL 请求超时

发布于 2025-01-20 20:38:57 字数 885 浏览 4 评论 0原文

我有一个应用程序,我可以在其中调用 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),但该解决方案有明显的缺陷。

另一种解决方法是捕获超时错误并再次调用加载方法。这也不完美,因为请求永远不会超时(即使在相关情况下)。

有什么建议如何处理这种情况?非常感谢

I have an app where I call API request. Some users are experiencing a bug, that application throws timeout error when they close the app while data are being fetched, and later they open it.

I'm using standard URLSession data task as in example bellow:

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

One workaround is to call .retry(1) in view model from which I'm calling the method, but that solution has obvious flaws.

Another workaround is to catch timeout error and call the load method again. That is not perfect either as the request will never time out (even when its relevant case).

Any suggestions how to handle this situation? Many thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

╭ゆ眷念 2025-01-27 20:38:57

好吧,所以我解决了将以下代码放在我的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 using resume data),
/// but suspends them. Suspended 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 using resume data),
/// but suspends them. Suspended 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
}

当您暂停数据任务时,会话不会产生错误,因此无需在视图 / view / view / use case / service中过滤过滤量/无论您从哪里调用API调用。您要做的就是在用户打开应用程序 /输入屏幕时刷新远程数据。

Alright, so I have solved the problem putting the following code in my APIAdapter / APIManager component:

// 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 using resume data),
/// but suspends them. Suspended 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 using resume data),
/// but suspends them. Suspended 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
}

When you suspend data tasks, the session won't produce error, so there's no need to filter cancels in view model / view / use case / service / where ever you are calling the API calls from. All you have to do is to refresh remote data when user opens the app / enters the screen.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文