Как обработать ожидание завершения вложенного URLSession - PullRequest
0 голосов
/ 02 декабря 2018

У меня есть функция, которая обеспечивает слой поверх URLSession.Когда эта функция вызывается, я хотел бы проверить, не истек ли текущий токен доступа, если он истек, если бы он имел, я бы приостановил текущий вызов, сделал вызов, чтобы запросить новый токен, заменил существующую запись в связке ключей, затем продолжил свызов.

func profile(with endpoint: ProfilesEndpoint, method: HTTPMethod, body: String?, headers: [String: String]?, useAuthToken: Bool = true, completion: @escaping (Either<ProfileResponse>) -> Void) {
    var request = endpoint.request
    request.httpMethod = method.rawValue

    if let body = body {
        request.httpBody = body.data(using: .utf8)
    }

    if useAuthToken {
        if !AuthService.shared.isTokenValid {
            let group = DispatchGroup()
            group.enter()
                OAuthService.shared.requestRefreshToken()
            group.leave()
        }

        let (header, token) = AuthService.shared.createAuthHeaderForNetworkRequest()
        request.addValue(token, forHTTPHeaderField: header)
    }

    if let headers = headers {
        for (key, value) in headers {
            request.addValue(value, forHTTPHeaderField: key)
        }
    }

    execute(with: request, completion: completion)
}

Существует механизм для работы с цепочкой для ключей, поэтому, пожалуйста, предположите, что он на месте.

Функция для запроса нового токена выглядит как

func requestRefreshToken() -> Void {

    if let refreshToken = KeychainWrapper.standard.string(forKey: "RefreshToken") {

        var postBody = "grant_type=\(refreshTokenGrantType)&"
        postBody += "client_id=\(clientId)&"
        postBody += "refresh_token=\(refreshToken)&"

        let additionalHeaders = [
            "Content-Type": "application/x-www-form-urlencoded;"
        ]

        APIClient.shared.identity(with: .token, method: .post, body: postBody, headers: additionalHeaders, useAuthToken: false) { either in
            switch either {
            case .success(let results):
                guard let accessToken = results.accessToken, let refreshToken = results.refreshToken else { return }
                AuthService.shared.addTokensToKeyChain(tokens: ["AccessToken": accessToken, "RefreshToken": refreshToken])
            case .error(let error):
                print("Error:", error)
            }
        }
    }
}

Я ожидал, что выполнение приостановится здесь

     group.enter()
            OAuthService.shared.requestRefreshToken()
        group.leave()

Однако это не так.

Как можно дождаться завершения этого вызова перед завершением остальной части функции?

Ответы [ 2 ]

0 голосов
/ 03 декабря 2018

Почему бы не сделать это снизу, а не сверху?Это похоже на идеальное использование для NSURLProtocol.В основном:

  • Протокол URL задерживает создание запроса в своем методе init и сохраняет все предоставленные параметры.
  • Протокол URL выделяет частный экземпляр NSURLSession и сохраняет егов собственности.Этот сеанс должен не быть настроен на использование протокола, иначе вы получите бесконечный цикл.
  • Когда протокол получает вызов startLoading (), он проверяет правильность токена, затем:
    • Запускает запрос на новый токен, если необходимо, в этом закрытом сеансе.
    • После ответа или, если токен все еще действителен, запускается реальный запрос - снова,в этом приватном сеансе.

При таком подходе весь процесс аутентификации становится в основном прозрачным для приложения, за исключением необходимости добавлять протокол в protocolClasses в конфигурации сеансапри создании нового сеанса.

(Существует несколько веб-сайтов, включая developer.apple.com, которые предоставляют примеры пользовательских подклассов NSURLProtocol; если вы решите использовать этот подход, вам, вероятно, следует использоватьодин из этих примеров кода проектов в качестве отправной точки.)

В качестве альтернативы, если вы хотите придерживаться подхода «слой сверху», вам нужно остановить этоПодумайте об «остановке» выполнения метода и начните думать об этом как о «выполнении последней части метода позже».Все дело в асинхронном мышлении.

В основном:

  • Выделяем последнюю часть метода (код, выполняющий фактический запрос) в новый метод (или блок).
  • Если токен действителен, немедленно вызвать этот метод.
  • Если токен недействителен, асинхронно получить новый токен.
  • В обратном вызове завершения вызова токена вызватьметод для выполнения фактического запроса.
0 голосов
/ 02 декабря 2018

Добавьте к вашему requestRefreshToken обработчику завершения метода, который будет выполнен, когда ваш запрос на токен будет завершен

func requestRefreshToken(_ completion: @escaping () -> Void) {

    if let refreshToken = KeychainWrapper.standard.string(forKey: "RefreshToken") {

        var postBody = "grant_type=\(refreshTokenGrantType)&"
        postBody += "client_id=\(clientId)&"
        postBody += "refresh_token=\(refreshToken)&"

        let additionalHeaders = [
            "Content-Type": "application/x-www-form-urlencoded;"
        ]

        APIClient.shared.identity(with: .token, method: .post, body: postBody, headers: additionalHeaders, useAuthToken: false) { either in

            switch either {
            case .success(let results):
                guard let accessToken = results.accessToken, let refreshToken = results.refreshToken else { 
                    completion()
                    return 
                }
                AuthService.shared.addTokensToKeyChain(tokens: ["AccessToken": accessToken, "RefreshToken": refreshToken])
            case .error(let error):
                print("Error:", error)
            }

            completion()
        }
    }
}

, затем оставьте dispatchGroup в закрытии и также добавьте group.wait() (после вызова метода запроса) для приостановки текущего потока до тех пор, пока задача группы не будет выполнена

group.enter()
    OAuthService.shared.requestRefreshToken {
        group.leave()
    }
group.wait()

Примечание: вы можете добавить логический параметр в завершение, чтобы проверить, был ли успешным запрос на токен или нет

...