ios - 使用 retryWhen 根据 http 错误代码更新 token

标签 ios swift alamofire rx-swift moya

我在 How to refresh oauth token using moya and rxswift 上找到了这个例子我不得不稍微改变一下才能编译。此代码对我的场景有效 80%。它的问题是它会针对所有 http 错误运行,而不仅仅是 401 错误。我想要的是将所有其他 http 错误作为错误传递,这样我就可以在其他地方处理它们,而不是在这里吞下它们。

使用这段代码,如果我得到 HttpStatus 500 ,它会运行3次验证码,这显然不是我想要的。

我试图更改此代码以仅处理句柄 401错误,但似乎无论我做什么我都无法编译代码。它总是提示错误的返回类型,"Cannot convert return expression of type Observable<Response> to return type Observable<Response>"这对我来说毫无意义..

我想要的:处理 401,但在出现所有其他错误时停止

import RxSwift
import KeychainAccess
import Moya

public extension ObservableType where E == Response {

  /// Tries to refresh auth token on 401 errors and retry the request.
  /// If the refresh fails, the signal errors.
  public func retryWithAuthIfNeeded() -> Observable<E> {
    return self.retryWhen {
      (e: Observable<ErrorType>) in
      return Observable.zip(e, Observable.range(start: 1, count: 3), resultSelector: { $1 })
        .flatMap { i in
          return AuthProvider.sharedInstance.request(
            .LoginFacebookUser(
              accessToken: AuthenticationManager.defaultInstance().getLoginTokenFromKeyChain(),
              useFaceBookLogin: AuthenticationManager.defaultInstance().isFacebookLogin())
            )
            .filterSuccessfulStatusCodes()
            .mapObject(Accesstoken.self)
            .catchError {
              error in
              log.debug("ReAuth error: \(error)")
              if case Error.StatusCode(let response) = error {
                if response.statusCode == 401 {
                  // Force logout after failed attempt
                  log.debug("401:, force user logout")
                  NSNotificationCenter.defaultCenter().postNotificationName(Constants.Notifications.userNotAuthenticated, object: nil, userInfo: nil)
                }
              }
              return Observable.error(error)
            }.flatMapLatest({
              token -> Observable<Accesstoken> in
              AuthenticationManager.defaultInstance().storeServiceTokenInKeychain(token)
              return Observable.just(token)
            })
      }
    }
  }
}

最佳答案

编译错误

哪一行有编译错误?在我看来,应该是这一行:

.catchError {
    error in
    //...
    return Observable.error(error)  // is this the line causing the compilation error?
}

如果是这样,可能是因为 catchError期望该 block 返回 Observable<Response>如果出现错误,它可以继续,而不是 Observable<ErrorType> .

无论哪种情况,它都有助于用更多类型注释您的代码,以便您可以查明此类问题,并帮助 Swift 编译器,而 Swift 编译器通常无法自行解决这些问题。所以像这样的事情会对你有所帮助:

.catchError {
    error -> Observable<Response> in
    //...
    return Observable.error(error)  // Swift should have a more accurate and helpful error message here now
}

请注意,我只是向您展示错误是什么以及如何让 Xcode 为您提供更好的错误消息。您尝试返回的内容仍然不正确。

仅在 401 上重试

我不确定您为什么期望此代码处理 401不同(除了发布到通知中心和日志记录)。实际上,您正在捕获错误,但您总是返回 ObservableError事件在最后 ( return Observable.error(error) ),所以它永远不会重试。

获取401要重试,您应该返回 Observable来自 retryWhen block ,它将发送 Next事件(表示您要重试)。对于所有其他状态代码,即 Observable应该发送 Error (正如您当前所做的那样),这将表示您不想重试,并且您希望传播错误。

所以像这样:

.retryWhen { errorObservable -> Observable<ErrorType> in
    log.debug("ReAuth error: \(error)")
    if case Error.StatusCode(let response) = error where response.statusCode == 401 {
        log.debug("401:, force user logout")
        NSNotificationCenter.defaultCenter().postNotificationName(Constants.Notifications.userNotAuthenticated, object: nil, userInfo: nil)
        // If `401`, then return the `Observable<ErrorType>` which was given to us
        // It will emit a `.Next<ErrorType>`
        // Since it is a `.Next` event, `retryWhen` will retry.
        return errorObservable
    }
    else {
        // If not `401`, then `flatMap` the `Observable<ErrorType>` which
        // is about to emit a `.Next<ErrorType>` into
        // an `Observable<ErrorType>` which will instead emit a `.Error<ErrorType>`.
        // Since it is an `.Error` event, `retryWhen` will *not* retry.
        // Instead, it will propagate the error.
        return errorObservable.flatMap { Observable.error($0) }
    }
}

关于ios - 使用 retryWhen 根据 http 错误代码更新 token ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38281760/

相关文章:

ios - 在 iOS 11 上使用 Xcode 8

iOS:自定义按钮(UIButton 的子类)- 无法更改边框属性

swift - NSDate 和 UIDatepicker Swift

ios - Swift 显示字典类型的 nil 值

swift - Alamofire OSX 上传视频

ios - 在 Alamofire 请求后,我是否需要 DispatchQueue.main 来更新 UI?

swift - 与 Alamofire 一起使用 UIImagePickerController 上传图像

ios - 部署目标上限

ios - 折线图无法正确显示

swift - 如何确定按下了哪个 UIBarButtomSytemItem