How to resolve an infinite loop in requestAccess(to:completion:) on EKEventStore?

The requestAuthorisation runs asynchronously, so confirmAuthorization Is getting called again before the authorization dialog has even been presented to the user.

Generally, in this sort of pattern (the desire to call something recursively in asynchronous patterns), the solution would be to move the recursive call to into the completion handler of the asynchronous method. But in this case, after the user gets the authorization dialog, they’ll either accept or decline, and there’s no point in worrying about the “what if it’s still not determined” state. So, bottom line, no recursion is needed or desired in this scenario.

That having been said, you obviously do want to get the status back to the caller. But the error throwing pattern won’t work because you need to handle the asynchronous situation (where the permission was not determined and we needed to present a confirmation dialog).

So I’d suggest, instead, that you use completion handler pattern throughout:

private func confirmAuthorization(for entityType: EKEntityType, completion: @escaping (EKAuthorizationStatus) -> Void) {
    let status = EKEventStore.authorizationStatus(for: entityType)

    switch status {
    case .notDetermined:
        requestAuthorisation(for: entityType, completion: completion)

    default:
        completion(status)
    }
}

private func requestAuthorisation(for entityType: EKEntityType, completion: @escaping (EKAuthorizationStatus) -> Void) {
    store.requestAccess(to: entityType) { _, _ in
        DispatchQueue.main.async {
            completion(EKEventStore.authorizationStatus(for: entityType))
        }
    }
}

Or, you can reduce that to a single method:

private func confirmAuthorization(for entityType: EKEntityType, completion: @escaping (EKAuthorizationStatus) -> Void) {
    let status = EKEventStore.authorizationStatus(for: entityType)

    switch status {
    case .notDetermined:
        store.requestAccess(to: entityType) { _, _ in
            DispatchQueue.main.async {
                completion(EKEventStore.authorizationStatus(for: entityType))
            }
        }

    default:
        completion(status)
    }
}

Then you can:

confirmAuthorization(for: .event) { status in
    switch status {
    case .authorized:
        // proceed

    default:
        // handle non-authorized process here
    }
}

// But, remember, the above runs asynchronously, so do *not*
// put any code contingent upon the auth status here. You 
// must put code contingent upon authorization inside the above
// completion handler closure.
//

// Request authorisation for the entity type.
requestAuthorisation(for: entityType)

is spawning a closure, which is being executed in a background thread. This means that the program continues and the result of this method call will be delivered at some point in the future. Problem is that:

// Switch again.
try confirmAuthorization(for: entityType)

is executed immediately afterwards ~ on the main thread ~ and spawns another background thread. You don't wait for these background threads to finish, before calling another background thread and so on. You need to rework the logic to wait for requestAuthorisation to return something before calling confirmAuthorization again...