I've been using Swift Concurrency for 2 years, but can't get my head around how to resolve this situation.
In a class named APIManager, there is an execute method that performs a network call.
When the server responds with a 401, the bearer token needs to be refreshed & the call retried.
Looks roughly like this:
class APIManager {
func execute<R: ResponseProtocol>(request: RequestProtocol, response: R) async throws -> R.Value {
let session = getURLSession()
let urlRequest: URLRequest = request.buildURLRequest()
let (data, urlResponse) = try await session.data(for: urlRequest)
if let httpURLResponse = urlResponse as? HTTPURLResponse,
httpURLResponse.statusCode == 401 {
try await renewBearerToken()
return try await execute(request: request, response: response)
}
return decode(data: data)
}
}
There is only one instance of this class (injected into every service). Tasks are fired randomly around the application (like on button clicks), and invoke this method.
What I would like to achieve is whenever the class is loading data, especially when refreshing a token, make sure any other parallel fired Task waits for its completion before also completing the call (or it might risk hitting that 401 too).
Any ideas if and how this can be enforced? TaskGroups, Actors?
You can save a reference to the
Taskfor the bearer token request, andawaitit:I would make the API manager an
actor(or isolate it to a global actor) to avoid races onbearerTask. And I check whether the bearer task isnilto avoid multiple, redundant bearer token requests if you just happened to quickly fire off many requests with the invalid token before the first of them gets around to returning the 401.But the key observation is that multiple API calls can
awaitthe samebearerTask.