Как разрешить бесконечный цикл в requestAccess (до: завершение :) на EKEventStore? - PullRequest
0 голосов
/ 08 января 2019

Я включаю EKAuthorizationStatus, но даже после того, как requestAuthorisation(to:commit:) вызван и возвращен true и без ошибок, оператор switch по-прежнему соответствует случаю .notDetermined, и рекурсия в нем создает бесконечный цикл. И это сводит меня с ума!

Я попытался выяснить, как на самом деле работает requestAuthorisation(to:commit:), так как у меня такое чувство, что эта проблема связана с параллелизмом или чем-то, но я не смог ничего найти, поэтому у меня возникли проблемы с истинным рассуждением о ситуации.

И поскольку рекурсия в моем коде определенно является частью этого бесконечного цикла, я попробовал подход без рекурсий. Но поскольку EKAuthorizationStatus может меняться между вызовами моего приложения в хранилище событий, я хочу проверить его заранее, реагируя на все его состояния соответственно. И поэтому мне пришлось бы вызывать мои методы для переключения статуса авторизации и один для запроса статуса и обрабатывать любые ошибки по всему классу, которые я не хочу делать из соображений читабельности, безопасности и здравомыслия.

private func confirmAuthorization(for entityType: EKEntityType) throws {
    switch EKEventStore.authorizationStatus(for: entityType) {
    case EKAuthorizationStatus.notDetermined:
        // Request authorisation for the entity type.
        requestAuthorisation(for: entityType)

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

    case EKAuthorizationStatus.denied:
        print("Access to the event store was denied.")
        throw EventHelperError.authorisationDenied

    case EKAuthorizationStatus.restricted:
        print("Access to the event store was restricted.")
        throw EventHelperError.authorisationRestricted

    case EKAuthorizationStatus.authorized:
        print("Acces to the event store granted.")
    }
}

private func requestAuthorisation(for entityType: EKEntityType) {
    store.requestAccess(to: entityType) { (granted, error)  in
        if (granted) && (error == nil) {
            DispatchQueue.main.async {
                print("User has granted access to \(String(describing: entityType))") // It's being printed over and over
            }
        } else {
            DispatchQueue.main.async {
                print("User has denied access to \(String(describing: entityType))")
            }
        }
    }
}

Я ожидал, что при первом запуске коммутатор будет соответствовать случаю .notDetermined, когда он запросит авторизацию. Поэтому, когда я снова переключаюсь через статус, он должен соответствовать другому регистру, например .authorized или .denied. Но на самом деле это снова соответствует случаю .notDetermined, и доступ предоставляется снова и снова. \>: [

Консоль

>2019-01-08 12:50:51.314628+0100 EventManager[4452:190572] libMobileGestalt MobileGestalt.c:890: MGIsDeviceOneOfType is not supported on this platform.
>2019-01-08 12:50:54.608391+0100 EventManager[4452:190572] Adding a new event.
>2019-01-08 12:50:54.784684+0100 EventManager[4452:190572] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /Users/***/Library/Developer/CoreSimulator/Devices/********-****-****-****-************/data/Containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
>2019-01-08 12:50:54.785638+0100 EventManager[4452:190572] [MC] Reading from private effective user settings.
>Acces to the event store granted.
>Saved event with identifier: Optional("F8EAC467-9EC2-476C-BF30-45588240A8D0:903EF489-BB52-4A86-917B-DF72494DEA3D")
>2019-01-08 12:51:03.019751+0100 EventManager[4452:190572] Events succsessfully saved.
>User has granted access to EKEntityType
>User has granted access to EKEntityType
>User has granted access to EKEntityType
>[…]
>User has granted access to EKEntityType
>User has granted access to EKEntityType
>User has granted access to EKEntityType
>2019-01-08 12:51:03.291606+0100 EventManager[4452:190572] [Common] _BSMachError: port 26b03; (os/kern) invalid capability (0x14) "Unable to insert COPY_SEND"
>2019-01-08 12:51:03.317800+0100 EventManager[4452:190572] [Common] _BSMachError: port 26b03; (os/kern) invalid capability (0x14) "Unable to insert COPY_SEND"
>User has granted access to EKEntityType
>User has granted access to EKEntityType
>User has granted access to EKEntityType
>[…]
>User has granted access to EKEntityType
>User has granted access to EKEntityType
>User has granted access to EKEntityType
>Acces to the event store granted.
>Preset <EventManager.EventCreationPreset: 0x6000020ca340> needs update.
>Acces to the event store granted.
>Preset <EventManager.EventCreationPreset: 0x6000020ca340> was updated.
>2019-01-08 12:51:03.567071+0100 EventManager[4452:190572] Events succsessfully saved.
>User has granted access to EKEntityType
>User has granted access to EKEntityType
>User has granted access to EKEntityType
>[…]

Ответы [ 2 ]

0 голосов
/ 07 февраля 2019

requestAuthorisation работает асинхронно, поэтому confirmAuthorization снова вызывается до того, как диалоговое окно авторизации будет представлено пользователю.

Как правило, в этом типе шаблона (желание вызывать что-либо рекурсивно в асинхронных шаблонах) решение состоит в том, чтобы переместить рекурсивный вызов в обработчик завершения асинхронного метода. Но в этом случае после того, как пользователь получит диалоговое окно авторизации, он либо примет, либо отклонит его, и нет смысла беспокоиться о состоянии «что, если оно еще не определено». Итак, суть в том, что в этом сценарии рекурсия не нужна или не нужна.

При этом вы, очевидно, do хотите вернуть статус вызывающей стороне. Но шаблон выдачи ошибок не сработает, потому что вам нужно обработать асинхронную ситуацию (когда разрешение не было определено, и нам нужно было представить диалоговое окно подтверждения).

Поэтому я предлагаю вместо этого использовать шаблон обработчика завершения:

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))
        }
    }
}

Или вы можете уменьшить это до одного метода:

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)
    }
}

Тогда вы можете:

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.
//
0 голосов
/ 07 февраля 2019
// Request authorisation for the entity type.
requestAuthorisation(for: entityType)

порождает замыкание, которое выполняется в фоновом потоке. Это означает, что программа продолжается, и результат этого вызова метода будет доставлен в определенный момент в будущем. Проблема в том, что:

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

выполняется сразу после этого ~ в главном потоке ~ и порождает другой фоновый поток. Вы не ждете окончания этих фоновых потоков, прежде чем вызывать другой фоновый поток и так далее. Вам нужно переработать логику, чтобы дождаться, пока requestAuthorisation вернет что-то, прежде чем снова вызывать confirmAuthorization ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...