Подтвердить что ты не робот

Alamofire: как обрабатывать ошибки во всем мире

Мой вопрос очень похож на этот, но для Alamofire: AFNetworking: обрабатывать ошибку во всем мире и повторять запрос

Как уловить глобальную ошибку (обычно 401) и обрабатывать ее до того, как будут сделаны другие запросы (и, в конечном счете, не удалось, если не удалось)?

Я думал о привязке настраиваемого обработчика ответов, но это глупо делать это при каждом запросе приложения.
Может быть, подклассификация, но какой класс должен подкласс для этого?

4b9b3361

Ответ 1

Обработка обновления для 401 ответов в потоке oauth довольно сложна, учитывая параллельность NSURLSessions. Я потратил довольно много времени на создание внутреннего решения, которое очень хорошо работало для нас. Ниже приведено очень высокое изложение общей идеи о том, как оно было реализовано.

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
  • Анализ ответа
  • Передача анализируемого ответа на соответствующий тип (generics)

Надеюсь, это поможет пролить свет.


Update

Теперь мы выпустили 🔥🔥 Alamofire 4.0 🔥🔥, который добавляет протоколы RequestAdapter и RequestRetrier, позволяющие вам легко создавать собственную систему аутентификации независимо от деталей реализации авторизации! Для получения дополнительной информации см. наш README, в котором приведен полный пример того, как вы можете реализовать систему OAuth2 в своем приложении.

Полное раскрытие: Пример в README предназначен только для использования в качестве примера. Пожалуйста, пожалуйста, не просто переходите и скопируйте код в производственное приложение.

Ответ 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)
}

Ответ 3

APIDriverAcountClient.refreshToken(refreshToken: refreshToken)...

Хамед сафари, пожалуйста, покажите реализацию метода refreshToken