如何解决 EKEventStore 上的 requestAccess(to:completion:) 中的无限循环?
How to resolve an infinite loop in requestAccess(to:completion:) on EKEventStore?
我正在切换 EKAuthorizationStatus
但即使在 requestAuthorisation(to:commit:)
被调用并返回 true 并且没有错误之后 switch 语句仍然匹配 .notDetermined
情况并且其中的递归正在产生无限循环。这让我抓狂!
我试图找出 requestAuthorisation(to:commit:)
是如何工作的,因为我觉得这个问题完全与并发性或其他问题有关,但我找不到任何东西,所以我很难真正理解这种情况。
而且由于我代码中的递归肯定是这个无限循环的一部分,所以我尝试了一种没有递归的方法。
但是由于 EKAuthorizationStatus
可能会在我的应用程序对事件存储的调用之间发生变化,我想事先检查它以相应地对所有状态做出反应。因此,我将不得不调用我的方法来切换授权状态和一个请求它的方法,并处理我 class 中的任何错误,出于可读性、安全性和健全性的原因,我不想这样做。
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
>[…]
// Request authorisation for the entity type.
requestAuthorisation(for: entityType)
正在生成一个正在后台线程中执行的闭包。这意味着程序会继续,并且此方法调用的结果将在将来的某个时间点传递。问题是:
// Switch again.
try confirmAuthorization(for: entityType)
之后立即执行 ~ 在主线程上 ~ 并产生另一个后台线程。在调用另一个后台线程等之前,您不必等待这些后台线程完成。在再次调用 confirmAuthorization
之前,您需要重新编写等待 requestAuthorisation
到 return 的逻辑...
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.
//
我正在切换 EKAuthorizationStatus
但即使在 requestAuthorisation(to:commit:)
被调用并返回 true 并且没有错误之后 switch 语句仍然匹配 .notDetermined
情况并且其中的递归正在产生无限循环。这让我抓狂!
我试图找出 requestAuthorisation(to:commit:)
是如何工作的,因为我觉得这个问题完全与并发性或其他问题有关,但我找不到任何东西,所以我很难真正理解这种情况。
而且由于我代码中的递归肯定是这个无限循环的一部分,所以我尝试了一种没有递归的方法。
但是由于 EKAuthorizationStatus
可能会在我的应用程序对事件存储的调用之间发生变化,我想事先检查它以相应地对所有状态做出反应。因此,我将不得不调用我的方法来切换授权状态和一个请求它的方法,并处理我 class 中的任何错误,出于可读性、安全性和健全性的原因,我不想这样做。
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
>[…]
// Request authorisation for the entity type.
requestAuthorisation(for: entityType)
正在生成一个正在后台线程中执行的闭包。这意味着程序会继续,并且此方法调用的结果将在将来的某个时间点传递。问题是:
// Switch again.
try confirmAuthorization(for: entityType)
之后立即执行 ~ 在主线程上 ~ 并产生另一个后台线程。在调用另一个后台线程等之前,您不必等待这些后台线程完成。在再次调用 confirmAuthorization
之前,您需要重新编写等待 requestAuthorisation
到 return 的逻辑...
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.
//