Понимание примера AlamoFire OAuth
Я смог получить рабочую реализацию примера OAuth, предоставленного AlamoFire. Тем не менее, я хочу понять некоторые строки кода и то, как он работает.
Полный пример:
class OAuth2Handler: RequestAdapter, RequestRetrier {
private typealias RefreshCompletion = (_ succeeded: Bool, _ accessToken: String?, _ refreshToken: String?) -> Void
private let sessionManager: SessionManager = {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
return SessionManager(configuration: configuration)
}()
private let lock = NSLock()
private var clientID: String
private var baseURLString: String
private var accessToken: String
private var refreshToken: String
private var isRefreshing = false
private var requestsToRetry: [RequestRetryCompletion] = []
// MARK: - Initialization
public init(clientID: String, baseURLString: String, accessToken: String, refreshToken: String) {
self.clientID = clientID
self.baseURLString = baseURLString
self.accessToken = accessToken
self.refreshToken = refreshToken
}
// MARK: - RequestAdapter
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix(baseURLString) {
var urlRequest = urlRequest
urlRequest.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
return urlRequest
}
return urlRequest
}
// MARK: - RequestRetrier
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
lock.lock() ; defer { lock.unlock() }
if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 {
requestsToRetry.append(completion)
if !isRefreshing {
refreshTokens { [weak self] succeeded, accessToken, refreshToken in
guard let strongSelf = self else { return }
strongSelf.lock.lock() ; defer { strongSelf.lock.unlock() }
if let accessToken = accessToken, let refreshToken = refreshToken {
strongSelf.accessToken = accessToken
strongSelf.refreshToken = refreshToken
}
strongSelf.requestsToRetry.forEach { $0(succeeded, 0.0) }
strongSelf.requestsToRetry.removeAll()
}
}
} else {
completion(false, 0.0)
}
}
// MARK: - Private - Refresh Tokens
private func refreshTokens(completion: @escaping RefreshCompletion) {
guard !isRefreshing else { return }
isRefreshing = true
let urlString = "\(baseURLString)/oauth2/token"
let parameters: [String: Any] = [
"access_token": accessToken,
"refresh_token": refreshToken,
"client_id": clientID,
"grant_type": "refresh_token"
]
sessionManager.request(urlString, method: .post, parameters: parameters, encoding: JSONEncoding.default)
.responseJSON { [weak self] response in
guard let strongSelf = self else { return }
if
let json = response.result.value as? [String: Any],
let accessToken = json["access_token"] as? String,
let refreshToken = json["refresh_token"] as? String
{
completion(true, accessToken, refreshToken)
} else {
completion(false, nil, nil)
}
strongSelf.isRefreshing = false
}
}
}
Вопросы:
[weak self] succeeded, accessToken, refreshToken in
guard let strongSelf = self else { return }
-
Какова цель [weak self]
и guard
для strongSelf
?
requestsToRetry.append(completion)
if !isRefreshing {
refreshTokens { [weak self] succeeded, accessToken, refreshToken in
guard let strongSelf = self else { return }
//Implementation
strongSelf.requestsToRetry.forEach { $0(succeeded, 0.0) }
strongSelf.requestsToRetry.removeAll()
}
}
-
Как работает эта попытка повторного запроса? requestsToRetry
- это всего лишь массив RequestRetryCompletion = (_ shouldRetry: Bool, _ timeDelay: TimeInterval)
. Как он узнает, какие запросы повторить?
strongSelf.lock.lock()
- Может ли
NSLock
не разрешать self
(OAuth2Handler) доступ к любому другому потоку во время выполнения этого метода?
Ответы
Ответ 1
1) Точно так же, как прокомментировал Fonix, у вас есть сильная ссылка на self
, чтобы избежать этого, если self
было нуль, вы начнете собирать циклы сохранения.
Я хочу:
[weak self] ... in
guard let strongSelf = self else { return }
Так как self
будет зафиксирован в блоке, который будет отправлен асинхронно, self
будет неявно сохранен и снова выпущен, когда блок будет закончен, другими словами self
будет продлен до тех пор, пока блок не закончит, Создавая этот код, вы избегаете продления срока службы self
и решаете не выполнять блок, если self
равно nil
2) В соответствии с указанными вами строками:
if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 {
requestsToRetry.append(completion)
..
существует массив с именем requestsToRetry
, который содержит весь запрос, который требуется перезапустить. В этом коде вы добавляете к массиву весь запрос, у которого есть код состояния 401
(когда сервер возвращает код состояния 401)
С кодом forEach
вы выполняете цикл requestToRetry
:
strongSelf.requestsToRetry.forEach { $0(succeeded, 0.0) }
strongSelf.requestsToRetry.removeAll()
и запустите все элементы. После завершения цикла вы удалите все элементы.
Фактически, источники сообщают:
public typealias RequestRetryCompletion = (_ shouldRetry: Bool, _ timeDelay: TimeInterval) -> Void
public protocol RequestRetrier {
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion)
}
Вы можете найти более подробную информацию здесь
3) Как вы сказали, часто возникают проблемы с concurrency, связанные с доступом/изменением общего ресурса из разных потоков.
lock.lock()
- это решение для блокировки других блоков выполнения, когда элементы изменяются. Код в defer
вызывается непосредственно перед тем, как оставить функцию для разблокировки блока.