如何解决EKEventStore上的requestAccess(to:completion :)中的无限循环?

时间:2019-01-08 15:27:11

标签: swift recursion infinite-loop eventkit 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
>[…]

2 个答案:

答案 0 :(得分:5)

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.
//

答案 1 :(得分:2)

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

产生一个闭包,该闭包在后台线程中执行。这意味着程序将继续执行,并且此方法调用的结果将在将来的某个时候传递。问题是:

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

之后立即在主线程上执行〜,并生成另一个后台线程。您不必等待这些后台线程完成,再调用另一个后台线程,依此类推。您需要重新设计逻辑,以等待requestAuthorisation返回某些内容,然后再次调用confirmAuthorization ...