I was able to get a working implementation of OAuth example as provided by AlamoFire. However, I am looking to understand certain lines of code and how it works.
Full Example:
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
}
}
}
Questions:
[weak self] succeeded, accessToken, refreshToken in
guard let strongSelf = self else { return }
What is the purpose of [weak self]
and the guard
for 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()
}
}
How does this request retry work? The requestsToRetry
is just an array of RequestRetryCompletion = (_ shouldRetry: Bool, _ timeDelay: TimeInterval)
How does it know what requests to retry?
strongSelf.lock.lock()
NSLock
just not allow self
(OAuth2Handler) to be accessed by any other thread while this method is executing?1) Exactly as commented by Fonix, you have a strong reference to self
to avoid that if self
was nil you start to collect retain cycles..
I'm refeer to :
[weak self] ... in
guard let strongSelf = self else { return }
Since self
will be captured in the block which is dispatched asynchronously, self
will be implicitly retained and released again when the block has been finished, in other words self
will be extended up until after the block finishes. Making this code, you avoid to extend the life-time of self
and decide to don't execute the block if self
is equal to nil
2) According to the lines you mentioned:
if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 {
requestsToRetry.append(completion)
..
there is an array called requestsToRetry
which contains all the request you need to relaunch. In this code you append to the array all the request that have the 401
status code (when server returns status code 401)
With the code forEach
you loop through the requestToRetry
array:
strongSelf.requestsToRetry.forEach { $0(succeeded, 0.0) }
strongSelf.requestsToRetry.removeAll()
and launch all items. After the cycle is concluded you remove all the items.
In fact, the sources report:
public typealias RequestRetryCompletion = (_ shouldRetry: Bool, _ timeDelay: TimeInterval) -> Void
public protocol RequestRetrier {
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion)
}
You can find more details here
3) Exactly as you said the frequently concurrency issues faced are the one related to accessing/modifying the shared resource from different threads.
The lock.lock()
is a solution to lock the other execution blocks when items is being modified. The code in defer
is called just before leaving the function to unlocking the block.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With