【问题标题】:URL request times out when app is in the background应用在后台时 URL 请求超时
【发布时间】:2022-06-15 16:11:58
【问题描述】:

我有一个调用 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),但该解决方案存在明显缺陷。

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

任何建议如何处理这种情况?非常感谢

【问题讨论】:

    标签: ios swift urlsession background-task


    【解决方案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 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
    }
    

    当您暂停数据任务时,会话不会产生错误,因此无需在视图模型/视图/用例/服务/您调用 API 调用的位置过滤取消。您所要做的就是在用户打开应用/进入屏幕时刷新远程数据。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-02-26
      • 2020-10-13
      • 2016-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多