重试URLSession dataTask的模式?

15

我对iOS/Swift开发比较新,并且正在开发一个向REST API发送多个请求的应用程序。以下是其中一个调用的示例,用于检索“消息”:

func getMessages() {

    let endpoint = "/api/outgoingMessages"

    let parameters: [String: Any] = [
        "limit" : 100,
        "sortOrder" : "ASC"
    ]

    guard let url = createURLWithComponents(endpoint: endpoint, parameters: parameters) else {
        print("Failed to create URL!")
        return
    }

    do {
        var request = try URLRequest(url: url, method: .get)

        let task = URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in

            if let error = error {
                print("Request failed with error: \(error)")
                // TODO: retry failed request
            } else if let data = data, let response = response as? HTTPURLResponse {                
                if response.statusCode == 200 {
                    // process data here
                } else {
                    // TODO: retry failed request
                }
            }
        }

        task.resume()

    } catch {
        print("Failed to construct URL: \(error)")
    }
}

当然,由于各种不同的原因(服务器无法访问,请求超时,服务器返回除了200之外的其他内容等),这个请求有可能失败。如果我的请求失败了,我希望能够重新尝试,并且在下一次尝试之前进行延迟处理。虽然我在苹果的文档中没有看到关于这种情况的指导,但我在SO上找到了几个相关的讨论。不幸的是,这些讨论都是几年前的,而且使用的是我从未接触过的Objective-C。在Swift中,是否存在常见的模式或实现方式来执行此类操作?


这要看情况,这些请求是否与用户界面相关联?(即您是否需要从服务器获取数据来显示内容)。还是仅仅是后端逻辑,您需要将应用程序中的数据存储到服务器上。 - TNguyen
1
对于 UI 部分,有很多处理方式,但通常情况下,我感觉一般的标准是让用户知道他们没有网络,并允许用户在有网络时刷新(或者你可以使用可达性并持续侦听等待网络连接可用,然后发出请求,还有许多其他方式)。至于后端部分,您需要尽可能及时地将数据更新到数据库中吗?如果稍晚一点到达,那么这是否重要?您可以看到这在重试逻辑中扮演了巨大的角色。 - TNguyen
@TNguyen感谢您的建议。我现在实际上正在使用可达性来了解我是否有连接。我在问题中展示的示例请求是由服务器发送的推送通知触发的。用户可能没有查看显示该数据的应用程序部分,但期望数据将被立即获取并插入数据库中。 - bmt22033
我不再过多地使用远程通知,但是你不能只是通过远程通知推送所需的数据而不是调用服务器吗?此外,如果您希望立即将数据插入数据库,则可能会在后台使用定时器重复尝试,就像您在OP中提到的那样。但明显的问题是当用户离开应用程序或终止它时,在这种情况下,您就没有运气了,并且必须在他们再次打开应用程序时重新开始重试(也许在applicationDidBecomeActive中添加一些内容)。 - TNguyen
我觉得对于UI,它将是背景重试逻辑和向用户显示没有互联网并允许他们手动重试的组合(尽管在没有看到项目细节的情况下很难说)。 - TNguyen
显示剩余4条评论
2个回答

11

这个问题更倾向于基于观点,并且比较广泛,但我敢打赌大多数情况下都是类似的。

对于触发UI变化的数据更新:

(例如:用数据填充的表格或图像加载)通常的经验法则是以非阻碍用户的方式通知用户,如下所示:

然后添加一个下拉刷新控件或刷新按钮。

对于不影响用户操作或行为的后台数据更新:

您可以根据代码轻松地将重试计数器添加到请求结果中,但我建议小心处理并构建一些更智能的逻辑。例如,根据以下状态码,您可能需要以不同的方式处理:

  • 5xx: 服务器出现问题。您可能需要延迟30秒或一分钟进行重试,但如果发生3次或4次,则需要停止访问后端。

  • 401: 经过身份验证的用户可能不再被授权调用API。您不想重试这个请求;相反,您可能想注销该用户,以便下次使用您的应用程序时提示重新进行身份验证。

  • 网络超时/连接丢失:在重新建立连接之前重试是无关紧要的。您可以编写一些逻辑,围绕您的可达性处理程序,以便将后台请求排队等待下次网络连接可用时执行。

最后,正如我们在评论中谈到的那样,您可能需要查看基于通知的后台应用程序刷新。这是一种不需要轮询服务器进行更改的方法,您可以发送通知告诉应用程序在前台不运行时更新自己。如果您足够聪明,您的服务器可以重复向应用程序发送通知,直到应用程序已确认接收 - 这以一致的方式解决了连接失败和其他服务器响应错误代码的问题。


1
你提出了很好的观点。但是对于401完全不重试,我会小心一些。这可能会导致潜在丢失重要数据。 - TNguyen
同样地,对于500,你需要再次小心。只是因为它是5xx并且您重试了x次并不意味着您应该停止重试。获取5xx并不总是意味着您的服务器存在问题。它可能具有完美的逻辑,但有时仍然必须返回5xx(例如当您尝试写入您的数据库而您的数据库关闭时)。还有许多其他原因,让您仍然希望在5xx上一直尝试重试。 - TNguyen
如果从服务器返回401,那么很有可能无论你重试多少次都无法获取数据,直到应用程序代表用户收到了新的令牌。 - brandonscript
1
您需要设置应用程序以响应通知,发送请求并包含通知ID。当服务器接收到具有相同通知ID的API调用时,您可以将该通知标记为已传送。这使您可以将重试逻辑编写到服务器而不是客户端中。 - brandonscript
在 401 错误时,您不应该自动注销用户。这会导致糟糕的体验,例如在 iPhone 屏幕上一遍又一遍地重新输入用户名和密码,因为身份验证服务器出现问题。相反,您应该在一段时间后静默重试,并显示提供有关用户是否最近更改其密码以及再次登录的信息通知。始终提供信息,但不要强迫用户采取行动。 - dgatwood
显示剩余10条评论

9
我将把处理重试的三种方法分类:
  1. 可达性重试
  • 可达性是指“让我知道网络连接何时发生了变化”的一种花哨方式。苹果提供了一些片段,但它们看起来并不好玩 - 我的建议是使用像Ashley Mill的Reachability替代品。
  • 除了可达性外,苹果还提供了一个waitsForConnectivity属性,您可以在URLSession配置上设置该属性。通过设置它,当任务正在等待网络连接时,您会通过URLSessionDataDelegate收到通知。您可以利用这个机会启用离线模式或向用户显示某些内容。
  1. 手动重试
  • 让用户决定何时重试请求。我认为这通常使用“下拉刷新”手势/界面实现。
  1. 定时/自动重试
  • 等待几秒钟后再次尝试。
  • Apple的Combine框架提供了一种方便的方式来重试失败的网络请求。请参阅使用Combine处理URL会话数据任务结果
  • Apple文档:URL会话的生命周期(已弃用)...您的应用程序不应立即重试[请求]。相反,它应该使用可达性API来确定服务器是否可达,并在收到可达性已更改的通知时才发出新请求。

2
好的评论。我建议添加关于何时使用每种方法的信息。如果一个操作是由用户发起的“获取数据”操作,并且用户正在等待响应,您应该显示错误,并允许用户选择何时重试,而不是让屏幕意外刷新。如果一个操作是由用户发起的“发布数据”操作,应用程序应告诉用户它现在无法发布,但当用户上线时,它将自动发布。对于后台请求,请始终使用可达性。 - dgatwood
1
我还要补充一点,可达性状态可能会误导你,因此即使之前的请求失败了,也不要将任何初始请求都基于可达性进行限制。首先尝试,如果尝试失败再使用可达性。在高延迟环境中,对于幂等请求,以指数退避的方式延迟并行尝试两到三次可能也是有益的,只有在[n]次失败后才使用可达性,因为数据包丢失往往是突发性的。 - dgatwood
文档中没有提供有关重试生命周期的更多信息。 - Ramis
@Ramis,我更新了我的答案,显示该链接已过时,并添加了一个新链接,指向Combine框架中可用的重试。 - jarrodparkes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接