How can I use DispatchSemaphore with a Closure

1.6k Views Asked by At

I have a value that looks like this

   lazy var authHeaders: [String: String] = {
        if shouldTokenBeRefreshed() {
            let semaphore = DispatchSemaphore(value: 0)
            refreshTokens {
                semaphore.signal()
            }
            semaphore.wait()
        }
        return ["Authorization": "Bearer \(module.client.credential.oauthToken)"]
    }()

The idea is, when requesting my auth headers, if my token has expired I will refresh it and then return the new value instead.

func refreshTokens(completion: @escaping () -> Void) {
    guard let token = keychain.get("refresh_token") else { return }

    module.renewAccessToken(
        withRefreshToken: token,
        success: { [weak self] credential, response, parameters in
            guard let self = self else { return }
            self.storeTokens([
                "access_token": credential.oauthToken,
                "refresh_token": credential.oauthRefreshToken
            ])
            completion()
        },
        failure: { error in
            print(error.description)
        })
}

As this is an async operation, I have tried to pause the flow using a Semaphore so I can cause it to continue once the completion block is triggered.

The call is not resolving however and I am unsure why.

2

There are 2 best solutions below

1
On BEST ANSWER

This is not how you should use DispatchSemaphore.

Please do not force async code to be synchronous.

You will need to refactor your code to better handle the async nature of what you are trying to achieve.

A completion handler is the simpler, more efficient way to go. If you are trying to avoid this for some reason, take a look at PromiseKit or something other async helper library.

An alternative suggestion would be rather than updating your tokens pre flight, update them on a 401 and then replay your original, updated request.

0
On

I'm not sure what you mean by

The call is not resolving

but there are a couple things to note in your example.

  • Be sure that authHeaders are not being initialized on the main thread since the semaphore would block your UI.
  • The only way the semaphore would be signaled to stop waiting is if the completion closure is executed. There are various paths in your code where the completion closure would not execute. In refreshTokens failing the first guard or hitting the failure block would not execute the completion closure, thus the semaphore would not stop waiting.