I've a problem. I need that a resource it's ready to use it. I've implemented this mechanism:
func shouldRetry(request: Request, error: Error) -> Bool {
let semaphore = DispatchSemaphore(value: 0)
var shouldRetry: Bool = false
self.interceptor?.retry(request.response, for: self.session!, dueTo: error, completion: { (retryResult) in
switch retryResult {
case .retry:
shouldRetry = true
semaphore.signal()
case .doNotRetry:
shouldRetry = false
semaphore.signal()
case .retryWithDelay(let delay):
shouldRetry = true
self.retryTimer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false, block: { (_) in
semaphore.signal()
self.retryTimer?.invalidate()
self.retryTimer = nil
})
}
})
semaphore.wait()
return shouldRetry
}
The problem is that interceptor?.retry
func use is called in the same thread of semaphore, and this block the process. Any suggestions?
UPDATE:
I solved my question. I subclasses URLProtocol abstract class. This class works with URLSession and it's possible suspend a HTTP call, make async code and resume
I don't think there's an answer without more context. However, if you try to implement a "refresh-token adapter / retrier", the strategy is as follows:
You need a specialised adapter (a struct or class) which sets the Authorization header with the access token. Accessing a token MUST be thread-safe, and even getting a token may fail - for example, because there's none. In case of an error, the adapter tries NOT to get a new access token, this will be done in the retrier. The adapter may just not set the access token. The request will fail and will be handled in the retrier.
The adapter dispatches is functions on a dedicated queue, let's call it "process_queue". When finished setting the header it calls the completion handler.
You also need a specialised Retrier (a struct or class). It accesses a shared state which holds the result of a token request. This can be a Swift.Result for example.
This retrier's functions execute on a dedicated dispatch queue.
The specialised Retrier determines the response status and if the status code is a 401 (not authorised) AND if the Authorization header is a bearer token, it needs to make a refresh-token request.
When starting this refresh-token task, it suspenses the process_queue, so that no more request adaption take place with an expired access token.
Now, the token request completes and say it was successful. The retrier stores the access token (into the key chain), updates the original request with the new access token, then resumes the process_queue and then it calls its completion handler.
Proper Error handling makes this slightly more complex. The question is how to treat pending requests when the refresh-token request has failed. I have used an approach, which let fail all the pending requests that have been queued until the refresh-token request completed. They just fail with the error 401. Then a new cycle starts, beginning with attempting to get a new refresh-token.
This approach prevents that multiple failed requests call into the token endpoint at the same time. You can even extend the approach when the refresh token also expires. In that case you need to sign-in the user. All this may happen within the retrier. It completes only after sign-in is complete, after getting a new access token is complete, or when an error occurred.