Как разрешить бесконечный цикл в requestAccess(to:completion:) в EKEventStore?

Я включаю 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
>[…]

person Bernhard    schedule 08.01.2019    source источник
comment
вы не можете вызывать try? confirmAuthorization сразу после requestAuthorisation(for: entityType). вы получите EKAuthorizationStatus authorized или something после того, как пользователь выбрал что-то в Alert. Таким образом, вы должны снова вызвать try? confirmAuthorization в обратном вызове store.requestAccess. И нет необходимости в рекурсии.   -  person ChanOnly123    schedule 05.02.2019


Ответы (2)


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

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

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

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

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.
//
person Rob    schedule 07.02.2019
comment
И есть ли способ, которым я мог бы выдать ошибку из закрытия завершения? Я попытался определить тип завершения как (EKAuthorisationStatus) throws -> Void, но затем получил сообщение об ошибке Invalid conversion from throwing function of type '() throws -> Void' to non-throwing function type '@convention(block) () -> Void' в открывающей фигурной скобке DispatchQueue.main.async - person Bernhard; 10.02.2019
comment
@Bernhard - я понимаю интуицию («Мне нравится шаблон выдачи ошибок, чтобы определить, был ли запрос успешным или неудачным»), но нет, нет смысла делать это в асинхронном процессе. Вы уже вернулись с исходного вызова confirmAuthorization, так что больше нет способа отловить ошибку. И шаблон замыкания, который выдает ошибки, используется для движения в противоположном направлении, когда само замыкание, которое вы предоставляете, может выдать ошибку, которую будет проверять confirmAuthorization, а не наоборот. - person Rob; 10.02.2019
comment
@Bernhard - я бы посоветовал вам рассмотреть все API iOS на основе обработчиков завершения, которые могут дополнительно информировать вызывающую сторону об ошибке. Error? всегда предоставляется как параметр замыкания; закрытие не помечено throws. Как я уже сказал, единственное время, когда у вас есть замыкания throws, — это методы, в которых замыкания сами выполняют генерацию. Вот несколько надуманный пример: gist.github.com/robertmryan/088aa8abf2e8113df22d11a939250658 - person Rob; 10.02.2019

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

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

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

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

person mistakeNot    schedule 06.02.2019