【问题标题】:Alamofire : How to handle errors globallyAlamofire:如何全局处理错误
【发布时间】:2015-04-28 06:53:33
【问题描述】:

我的问题与这个问题非常相似,但对于 Alamofire:AFNetworking: Handle error globally and repeat request

如何能够在全局范围内捕获错误(通常是 401)并在其他请求发出之前对其进行处理(如果不进行管理最终会失败)?

我正在考虑链接一个自定义响应处理程序,但在应用程序的每个请求上都这样做很愚蠢。
也许是子类化,但我应该子类化哪个类来处理它?

【问题讨论】:

    标签: ios swift networking alamofire


    【解决方案1】:

    考虑到 NSURLSession 的并行性质,在 oauth 流中处理 401 响应的刷新非常复杂。我花了很长时间构建一个对我们非常有效的内部解决方案。以下是对其实施方式的一般概念的非常高级的提取。

    import Foundation
    import Alamofire
    
    public class AuthorizationManager: Manager {
        public typealias NetworkSuccessHandler = (AnyObject?) -> Void
        public typealias NetworkFailureHandler = (NSHTTPURLResponse?, AnyObject?, NSError) -> Void
    
        private typealias CachedTask = (NSHTTPURLResponse?, AnyObject?, NSError?) -> Void
    
        private var cachedTasks = Array<CachedTask>()
        private var isRefreshing = false
    
        public func startRequest(
            method method: Alamofire.Method,
            URLString: URLStringConvertible,
            parameters: [String: AnyObject]?,
            encoding: ParameterEncoding,
            success: NetworkSuccessHandler?,
            failure: NetworkFailureHandler?) -> Request?
        {
            let cachedTask: CachedTask = { [weak self] URLResponse, data, error in
                guard let strongSelf = self else { return }
    
                if let error = error {
                    failure?(URLResponse, data, error)
                } else {
                    strongSelf.startRequest(
                        method: method,
                        URLString: URLString,
                        parameters: parameters,
                        encoding: encoding,
                        success: success,
                        failure: failure
                    )
                }
            }
    
            if self.isRefreshing {
                self.cachedTasks.append(cachedTask)
                return nil
            }
    
            // Append your auth tokens here to your parameters
    
            let request = self.request(method, URLString, parameters: parameters, encoding: encoding)
    
            request.response { [weak self] request, response, data, error in
                guard let strongSelf = self else { return }
    
                if let response = response where response.statusCode == 401 {
                    strongSelf.cachedTasks.append(cachedTask)
                    strongSelf.refreshTokens()
                    return
                }
    
                if let error = error {
                    failure?(response, data, error)
                } else {
                    success?(data)
                }
            }
    
            return request
        }
    
        func refreshTokens() {
            self.isRefreshing = true
    
            // Make the refresh call and run the following in the success closure to restart the cached tasks
    
            let cachedTaskCopy = self.cachedTasks
            self.cachedTasks.removeAll()
            cachedTaskCopy.map { $0(nil, nil, nil) }
    
            self.isRefreshing = false
        }
    }
    

    这里要记住的最重要的事情是,您不想为每个返回的 401 运行刷新调用。大量请求可以同时竞争。因此,您希望对第一个 401 采取行动,并将所有其他请求排队,直到 401 成功。我上面概述的解决方案正是这样做的。任何通过startRequest 方法启动的数据任务如果遇到 401 都会自动刷新。

    在此非常简化的示例中未说明的其他一些需要注意的重要事项是:

    • 线程安全
    • 保证成功或失败的关闭调用
    • 存储和获取 oauth 令牌
    • 解析响应
    • 将解析后的响应转换为适当的类型(泛型)

    希望这有助于阐明一些问题。


    更新

    我们现在发布了?? Alamofire 4.0 ??,它添加了RequestAdapterRequestRetrier 协议,让您可以轻松构建自己的身份验证系统,而无需考虑授权实施细节!如需更多信息,请参阅我们的README,其中包含如何将 OAuth2 系统实施到您的应用中的完整示例。

    完全披露: README 中的示例仅用作示例。请不要只是将代码复制粘贴到生产应用程序中。

    【讨论】:

    • 没错。通过潜在 401 刷新流程的任何请求。
    • 好问题,articleone 涵盖了您需要了解的所有内容。您需要弱化/强化以避免不必要的保留周期。
    • 您需要保留 Manager,以免超出范围。我的意思是您应该在 Manager 上使用单例模式,或者将其作为属性存储在可能是单例的更大对象中,这样它就永远不会被释放。您需要始终将 Manager 实例保存在内存中,以便您可以正确执行刷新的任务。如果 Manager 被解除分配并且您总是最终创建新的,那么刷新将永远无法正常工作。
    • @cnoon nm,想通了。在调用cachedTasksCopy.map { $0(nil, nil, nil) } 之前设置self.isRefreshing = false 可以解决我的问题。在设置状态之前重新启动缓存会导致 cachedTask 不断地重新缓存。
    • @cnoon 首先,感谢这个很棒的 Alamofire 框架 :) 我是 swift 的新手,有一个非常简单的问题。如何将此类嵌入到我的项目中?能不能详细解释一下?
    【解决方案2】:

    在 Alamofire 5 中,您可以使用 RequestInterceptor 这是我在我的一个项目中对 401 错误的错误处理,我将 EnvironmentInterceptor 传递给它的每个请求,如果请求出错,将调用重试函数 并且适应函数可以帮助你为你的请求添加默认值

    struct EnvironmentInterceptor: RequestInterceptor {
    
    func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (AFResult<URLRequest>) -> Void) {
        var adaptedRequest = urlRequest
        guard let token = KeychainWrapper.standard.string(forKey: KeychainsKeys.token.rawValue) else {
            completion(.success(adaptedRequest))
            return
        }
        adaptedRequest.setValue("Bearer \(token)", forHTTPHeaderField: HTTPHeaderField.authentication.rawValue)
        completion(.success(adaptedRequest))
    }
    
    func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
        if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 {
            //get token
    
            guard let refreshToken = KeychainWrapper.standard.string(forKey: KeychainsKeys.refreshToken.rawValue) else {
                completion(.doNotRetryWithError(error))
                return
            }
    
            APIDriverAcountClient.refreshToken(refreshToken: refreshToken) { res in
                switch res {
                case .success(let response):
                    let saveAccessToken: Bool = KeychainWrapper.standard.set(response.accessToken, forKey: KeychainsKeys.token.rawValue)
                    let saveRefreshToken: Bool = KeychainWrapper.standard.set(response.refreshToken, forKey: KeychainsKeys.refreshToken.rawValue)
                    let saveUserId: Bool = KeychainWrapper.standard.set(response.userId, forKey: KeychainsKeys.uId.rawValue)
                    print("is accesstoken saved ?: \(saveAccessToken)")
                    print("is refreshToken saved ?: \(saveRefreshToken)")
                    print("is userID saved ?: \(saveUserId)")
                    completion(.retry)
                    break
                case .failure(let err):
                    //TODO logout
                    break
    
                }
    
            }
        } else {
            completion(.doNotRetry)
        }
    }
    

    你可以这样使用它:

    @discardableResult
    private static func performRequest<T: Decodable>(route: ApiDriverTrip, decoder: JSONDecoder = JSONDecoder(), completion: @escaping (AFResult<T>)->Void) -> DataRequest {
    
        return AF.request(route, interceptor: EnvironmentInterceptor())
            .responseDecodable (decoder: decoder){ (response: DataResponse<T>) in
             completion(response.result)
    }
    

    【讨论】:

      猜你喜欢
      • 2015-08-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-01
      • 2010-11-25
      • 1970-01-01
      • 1970-01-01
      • 2013-03-06
      相关资源
      最近更新 更多