来自 CNContactStore requestAccess 的错误 "A promise was finished incorrectly"
Error "A promise was finished incorrectly" from CNContactStore requestAccess
我正在构建一个 UI 以请求访问 CNContactStore 并处理用户先前(并且可能错误地)拒绝访问的情况。当我检测到当前状态为 .denied 时,我会显示一个 UIAlertController,解释并提议将它们带到应用程序设置以允许访问。
在当前状态为 .denied 的情况下调用 store.requestAccess() 时,应用程序崩溃并显示两个错误:1) "A promise was finished with a nil error." 和 2) "A promise was finished incorrectly."。调用堆栈如下所示。
我不擅长解释调用堆栈,但我认为错误来自 CNContactStore 内部。我不清楚我能做些什么来防止这个错误。
编辑:我根本没有在我的应用程序中使用承诺链。
EDIT2:上面明确说明了我的代码中发生错误的确切位置。
import UIKit
import Contacts
import ContactsUI
final class ContactsAppHelper {
static let shared = ContactsAppHelper()
var store = CNContactStore()
func checkAccessStatus(_ completionHandler: @escaping (_ accessGranted: Bool) -> Void) {
let authorizationStatus = CNContactStore.authorizationStatus(for: .contacts)
switch authorizationStatus {
case .authorized:
completionHandler(true)
case .denied, .notDetermined:
store.requestAccess(for: .contacts, completionHandler: { (access, accessError) -> Void in
if access {
completionHandler(access)
}
else {
print("access denied")
DispatchQueue.main.sync {
self.showSettingsAlert(completionHandler)
}
}
})
default:
completionHandler(false)
}
}
private func showSettingsAlert(_ completionHandler: @escaping (_ accessGranted: Bool) -> Void) {
let msg = "This app requires access to Contacts to proceed. Would you like to open settings and grant permission?"
let alert = UIAlertController(title: nil, message: msg, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Open Settings", style: .default) { action in
completionHandler(false)
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { action in
completionHandler(false)
})
let vc = UIApplication.getPresentedViewController()
vc!.present(alert, animated: true)
}
}
extension UIApplication{
class func getPresentedViewController() -> UIViewController? {
var presentViewController = UIApplication.shared.keyWindow?.rootViewController
while let pVC = presentViewController?.presentedViewController
{
presentViewController = pVC
}
return presentViewController
}
}
这些是产生的调用堆栈:
2019-06-14 15:59:12.220116-0700 Sales Networker[805:28798] [Rx] A promise was finished with a nil error.
2019-06-14 15:59:12.226500-0700 Sales Networker[805:28798] [Rx] Call stack: (
0 ContactsFoundation 0x000000012242312a -[CNFuture finishWithError:] + 52
1 Contacts 0x000000010e0c2a82 __55-[CNDataMapperContactStore requestAccessForEntityType:]_block_invoke_2 + 187
2 Contacts 0x000000010e0ffc71 +[CNAuthorization requestAccessForEntityType:completionHandler:] + 77
3 Contacts 0x000000010e072c44 -[CNXPCDataMapper requestAccessForEntityType:completionHandler:] + 123
4 Contacts 0x000000010e0c299a __55-[CNDataMapperContactStore requestAccessForEntityType:]_block_invoke + 174
5 libsystem_trace.dylib 0x000000011269cf00 os_activity_apply_f + 66
6 Contacts 0x000000010e116d52 -[_CNContactsLogger requestingAccessForContacts:] + 225
7 Contacts 0x000000010e0c28a4 -[CNDataMapperContactStore requestAccessForEntityType:] + 177
8 Contacts 0x000000010e09b77f -[CNContactStore requestAccessForEntityType:completionHandler:] + 54
9 Sales Networker 0x000000010ccaadc7 $s15Sales_Networker17ContactsAppHelperC17checkAccessStatusyyySbcF + 535
10 Sales Networker 0x000000010cce9673 $s15Sales_Networker24OnBoardingViewControllerC17rightButtonTappedyySo8UIButtonCF + 451
11 Sales Networker 0x000000010cce9ddc $s15Sales_Networker24OnBoardingViewControllerC17rightButtonTappedyySo8UIButtonCFTo + 60
12 UIKitCore 0x0000000119c7d204 -[UIApplication sendAction:to:from:forEvent:] + 83
13 UIKitCore 0x00000001196d2c19 -[UIControl sendAction:to:forEvent:] + 67
14 UIKitCore 0x00000001196d2f36 -[UIControl _sendActionsForEvents:withEvent:] + 450
15 UIKitCore 0x00000001196d1eec -[UIControl touchesEnded:withEvent:] + 583
16 UIKitCore 0x0000000119cb5eee -[UIWindow _sendTouchesForEvent:] + 2547
17 UIKitCore 0x0000000119cb75d2 -[UIWindow sendEvent:] + 4079
18 UIKitCore 0x0000000119c95d16 -[UIApplication sendEvent:] + 356
19 UIKitCore 0x0000000119d66293 __dispatchPreprocessedEventFromEventQueue + 3232
20 UIKitCore 0x0000000119d68bb9 __handleEventQueueInternal + 5911
21 CoreFoundation 0x000000010ebd6be1 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
22 CoreFoundation 0x000000010ebd6463 __CFRunLoopDoSources0 + 243
23 CoreFoundation 0x000000010ebd0b1f __CFRunLoopRun + 1231
24 CoreFoundation 0x000000010ebd0302 CFRunLoopRunSpecific + 626
25 GraphicsServices 0x0000000114a702fe GSEventRunModal + 65
26 UIKitCore 0x0000000119c7bba2 UIApplicationMain + 140
27 Sales Networker 0x000000010cca7f6b main + 75
28 libdyld.dylib 0x0000000112416541 start + 1
)
2019-06-14 15:59:12.236494-0700 Sales Networker[805:28798] [Rx] A promise was finished incorrectly.
2019-06-14 15:59:12.236582-0700 Sales Networker[805:28798] [Rx] Result: (null)
2019-06-14 15:59:12.236669-0700 Sales Networker[805:28798] [Rx] Error : (null)
2019-06-14 15:59:12.238150-0700 Sales Networker[805:28798] [Rx] Call stack: (
0 ContactsFoundation 0x0000000122422ed8 -[CNFuture finishWithResult:error:] + 290
1 Contacts 0x000000010e0c2a82 __55-[CNDataMapperContactStore requestAccessForEntityType:]_block_invoke_2 + 187
2 Contacts 0x000000010e0ffc71 +[CNAuthorization requestAccessForEntityType:completionHandler:] + 77
3 Contacts 0x000000010e072c44 -[CNXPCDataMapper requestAccessForEntityType:completionHandler:] + 123
4 Contacts 0x000000010e0c299a __55-[CNDataMapperContactStore requestAccessForEntityType:]_block_invoke + 174
5 libsystem_trace.dylib 0x000000011269cf00 os_activity_apply_f + 66
6 Contacts 0x000000010e116d52 -[_CNContactsLogger requestingAccessForContacts:] + 225
7 Contacts 0x000000010e0c28a4 -[CNDataMapperContactStore requestAccessForEntityType:] + 177
8 Contacts 0x000000010e09b77f -[CNContactStore requestAccessForEntityType:completionHandler:] + 54
9 Sales Networker 0x000000010ccaadc7 $s15Sales_Networker17ContactsAppHelperC17checkAccessStatusyyySbcF + 535
10 Sales Networker 0x000000010cce9673 $s15Sales_Networker24OnBoardingViewControllerC17rightButtonTappedyySo8UIButtonCF + 451
11 Sales Networker 0x000000010cce9ddc $s15Sales_Networker24OnBoardingViewControllerC17rightButtonTappedyySo8UIButtonCFTo + 60
12 UIKitCore 0x0000000119c7d204 -[UIApplication sendAction:to:from:forEvent:] + 83
13 UIKitCore 0x00000001196d2c19 -[UIControl sendAction:to:forEvent:] + 67
14 UIKitCore 0x00000001196d2f36 -[UIControl _sendActionsForEvents:withEvent:] + 450
15 UIKitCore 0x00000001196d1eec -[UIControl touchesEnded:withEvent:] + 583
16 UIKitCore 0x0000000119cb5eee -[UIWindow _sendTouchesForEvent:] + 2547
17 UIKitCore 0x0000000119cb75d2 -[UIWindow sendEvent:] + 4079
18 UIKitCore 0x0000000119c95d16 -[UIApplication sendEvent:] + 356
19 UIKitCore 0x0000000119d66293 __dispatchPreprocessedEventFromEventQueue + 3232
20 UIKitCore 0x0000000119d68bb9 __handleEventQueueInternal + 5911
21 CoreFoundation 0x000000010ebd6be1 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
22 CoreFoundation 0x000000010ebd6463 __CFRunLoopDoSources0 + 243
23 CoreFoundation 0x000000010ebd0b1f __CFRunLoopRun + 1231
24 CoreFoundation 0x000000010ebd0302 CFRunLoopRunSpecific + 626
25 GraphicsServices 0x0000000114a702fe GSEventRunModal + 65
26 UIKitCore 0x0000000119c7bba2 UIApplicationMain + 140
27 Sales Networker 0x000000010cca7f6b main + 75
28 libdyld.dylib 0x0000000112416541 start + 1
)
我测试了你的代码,发现这里发生了两件事。
首先是当您调用 requestAccess(for:completion:)
时状态已经是 .denied
,您会在控制台上得到一个 non-fatal 堆栈跟踪。您可以忽略此问题,也可以仅在 .notDetermined
状态下请求访问权限。
第二个问题与主队列上的同步调度有关。由于某种原因,这会导致访问冲突。解决方案是使用 asynchronous 调度。无论如何都没有充分的理由阻止调用队列。
func checkAccessStatus(_ completionHandler: @escaping (_ accessGranted: Bool) -> Void) {
let authorizationStatus = CNContactStore.authorizationStatus(for: .contacts)
switch authorizationStatus {
case .authorized:
completionHandler(true)
case .denied:
self.showSettingsAlert(completionHandler)
case .notDetermined:
store.requestAccess(for: .contacts, completionHandler: { (access, accessError) -> Void in
if access {
completionHandler(access)
}
else {
print("access denied")
self.showSettingsAlert(completionHandler)
}
})
default:
completionHandler(false)
}
}
private func showSettingsAlert(_ completionHandler: @escaping (_ accessGranted: Bool) -> Void) {
let msg = "This app requires access to Contacts to proceed. Would you like to open settings and grant permission?"
let alert = UIAlertController(title: nil, message: msg, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Open Settings", style: .default) { action in
completionHandler(false)
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { action in
completionHandler(false)
})
DispatchQueue.main.async {
if let vc = UIApplication.getPresentedViewController() {
vc.present(alert, animated: true)
} else {
completionHandler(false)
}
}
}
这个错误会在两种情况下出现。
当您向用户请求联系权限时,用户拒绝了。
用户已获得联系权限,稍后进入 settings -> your app > off contact permission
。
在日志中的这两种情况下,您将看到“承诺已完成,但出现零错误”。
根据客户要求,您可以显示权限被拒绝警报,也可以忽略它。
我正在构建一个 UI 以请求访问 CNContactStore 并处理用户先前(并且可能错误地)拒绝访问的情况。当我检测到当前状态为 .denied 时,我会显示一个 UIAlertController,解释并提议将它们带到应用程序设置以允许访问。
在当前状态为 .denied 的情况下调用 store.requestAccess() 时,应用程序崩溃并显示两个错误:1) "A promise was finished with a nil error." 和 2) "A promise was finished incorrectly."。调用堆栈如下所示。
我不擅长解释调用堆栈,但我认为错误来自 CNContactStore 内部。我不清楚我能做些什么来防止这个错误。
编辑:我根本没有在我的应用程序中使用承诺链。
EDIT2:上面明确说明了我的代码中发生错误的确切位置。
import UIKit
import Contacts
import ContactsUI
final class ContactsAppHelper {
static let shared = ContactsAppHelper()
var store = CNContactStore()
func checkAccessStatus(_ completionHandler: @escaping (_ accessGranted: Bool) -> Void) {
let authorizationStatus = CNContactStore.authorizationStatus(for: .contacts)
switch authorizationStatus {
case .authorized:
completionHandler(true)
case .denied, .notDetermined:
store.requestAccess(for: .contacts, completionHandler: { (access, accessError) -> Void in
if access {
completionHandler(access)
}
else {
print("access denied")
DispatchQueue.main.sync {
self.showSettingsAlert(completionHandler)
}
}
})
default:
completionHandler(false)
}
}
private func showSettingsAlert(_ completionHandler: @escaping (_ accessGranted: Bool) -> Void) {
let msg = "This app requires access to Contacts to proceed. Would you like to open settings and grant permission?"
let alert = UIAlertController(title: nil, message: msg, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Open Settings", style: .default) { action in
completionHandler(false)
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { action in
completionHandler(false)
})
let vc = UIApplication.getPresentedViewController()
vc!.present(alert, animated: true)
}
}
extension UIApplication{
class func getPresentedViewController() -> UIViewController? {
var presentViewController = UIApplication.shared.keyWindow?.rootViewController
while let pVC = presentViewController?.presentedViewController
{
presentViewController = pVC
}
return presentViewController
}
}
这些是产生的调用堆栈:
2019-06-14 15:59:12.220116-0700 Sales Networker[805:28798] [Rx] A promise was finished with a nil error.
2019-06-14 15:59:12.226500-0700 Sales Networker[805:28798] [Rx] Call stack: (
0 ContactsFoundation 0x000000012242312a -[CNFuture finishWithError:] + 52
1 Contacts 0x000000010e0c2a82 __55-[CNDataMapperContactStore requestAccessForEntityType:]_block_invoke_2 + 187
2 Contacts 0x000000010e0ffc71 +[CNAuthorization requestAccessForEntityType:completionHandler:] + 77
3 Contacts 0x000000010e072c44 -[CNXPCDataMapper requestAccessForEntityType:completionHandler:] + 123
4 Contacts 0x000000010e0c299a __55-[CNDataMapperContactStore requestAccessForEntityType:]_block_invoke + 174
5 libsystem_trace.dylib 0x000000011269cf00 os_activity_apply_f + 66
6 Contacts 0x000000010e116d52 -[_CNContactsLogger requestingAccessForContacts:] + 225
7 Contacts 0x000000010e0c28a4 -[CNDataMapperContactStore requestAccessForEntityType:] + 177
8 Contacts 0x000000010e09b77f -[CNContactStore requestAccessForEntityType:completionHandler:] + 54
9 Sales Networker 0x000000010ccaadc7 $s15Sales_Networker17ContactsAppHelperC17checkAccessStatusyyySbcF + 535
10 Sales Networker 0x000000010cce9673 $s15Sales_Networker24OnBoardingViewControllerC17rightButtonTappedyySo8UIButtonCF + 451
11 Sales Networker 0x000000010cce9ddc $s15Sales_Networker24OnBoardingViewControllerC17rightButtonTappedyySo8UIButtonCFTo + 60
12 UIKitCore 0x0000000119c7d204 -[UIApplication sendAction:to:from:forEvent:] + 83
13 UIKitCore 0x00000001196d2c19 -[UIControl sendAction:to:forEvent:] + 67
14 UIKitCore 0x00000001196d2f36 -[UIControl _sendActionsForEvents:withEvent:] + 450
15 UIKitCore 0x00000001196d1eec -[UIControl touchesEnded:withEvent:] + 583
16 UIKitCore 0x0000000119cb5eee -[UIWindow _sendTouchesForEvent:] + 2547
17 UIKitCore 0x0000000119cb75d2 -[UIWindow sendEvent:] + 4079
18 UIKitCore 0x0000000119c95d16 -[UIApplication sendEvent:] + 356
19 UIKitCore 0x0000000119d66293 __dispatchPreprocessedEventFromEventQueue + 3232
20 UIKitCore 0x0000000119d68bb9 __handleEventQueueInternal + 5911
21 CoreFoundation 0x000000010ebd6be1 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
22 CoreFoundation 0x000000010ebd6463 __CFRunLoopDoSources0 + 243
23 CoreFoundation 0x000000010ebd0b1f __CFRunLoopRun + 1231
24 CoreFoundation 0x000000010ebd0302 CFRunLoopRunSpecific + 626
25 GraphicsServices 0x0000000114a702fe GSEventRunModal + 65
26 UIKitCore 0x0000000119c7bba2 UIApplicationMain + 140
27 Sales Networker 0x000000010cca7f6b main + 75
28 libdyld.dylib 0x0000000112416541 start + 1
)
2019-06-14 15:59:12.236494-0700 Sales Networker[805:28798] [Rx] A promise was finished incorrectly.
2019-06-14 15:59:12.236582-0700 Sales Networker[805:28798] [Rx] Result: (null)
2019-06-14 15:59:12.236669-0700 Sales Networker[805:28798] [Rx] Error : (null)
2019-06-14 15:59:12.238150-0700 Sales Networker[805:28798] [Rx] Call stack: (
0 ContactsFoundation 0x0000000122422ed8 -[CNFuture finishWithResult:error:] + 290
1 Contacts 0x000000010e0c2a82 __55-[CNDataMapperContactStore requestAccessForEntityType:]_block_invoke_2 + 187
2 Contacts 0x000000010e0ffc71 +[CNAuthorization requestAccessForEntityType:completionHandler:] + 77
3 Contacts 0x000000010e072c44 -[CNXPCDataMapper requestAccessForEntityType:completionHandler:] + 123
4 Contacts 0x000000010e0c299a __55-[CNDataMapperContactStore requestAccessForEntityType:]_block_invoke + 174
5 libsystem_trace.dylib 0x000000011269cf00 os_activity_apply_f + 66
6 Contacts 0x000000010e116d52 -[_CNContactsLogger requestingAccessForContacts:] + 225
7 Contacts 0x000000010e0c28a4 -[CNDataMapperContactStore requestAccessForEntityType:] + 177
8 Contacts 0x000000010e09b77f -[CNContactStore requestAccessForEntityType:completionHandler:] + 54
9 Sales Networker 0x000000010ccaadc7 $s15Sales_Networker17ContactsAppHelperC17checkAccessStatusyyySbcF + 535
10 Sales Networker 0x000000010cce9673 $s15Sales_Networker24OnBoardingViewControllerC17rightButtonTappedyySo8UIButtonCF + 451
11 Sales Networker 0x000000010cce9ddc $s15Sales_Networker24OnBoardingViewControllerC17rightButtonTappedyySo8UIButtonCFTo + 60
12 UIKitCore 0x0000000119c7d204 -[UIApplication sendAction:to:from:forEvent:] + 83
13 UIKitCore 0x00000001196d2c19 -[UIControl sendAction:to:forEvent:] + 67
14 UIKitCore 0x00000001196d2f36 -[UIControl _sendActionsForEvents:withEvent:] + 450
15 UIKitCore 0x00000001196d1eec -[UIControl touchesEnded:withEvent:] + 583
16 UIKitCore 0x0000000119cb5eee -[UIWindow _sendTouchesForEvent:] + 2547
17 UIKitCore 0x0000000119cb75d2 -[UIWindow sendEvent:] + 4079
18 UIKitCore 0x0000000119c95d16 -[UIApplication sendEvent:] + 356
19 UIKitCore 0x0000000119d66293 __dispatchPreprocessedEventFromEventQueue + 3232
20 UIKitCore 0x0000000119d68bb9 __handleEventQueueInternal + 5911
21 CoreFoundation 0x000000010ebd6be1 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
22 CoreFoundation 0x000000010ebd6463 __CFRunLoopDoSources0 + 243
23 CoreFoundation 0x000000010ebd0b1f __CFRunLoopRun + 1231
24 CoreFoundation 0x000000010ebd0302 CFRunLoopRunSpecific + 626
25 GraphicsServices 0x0000000114a702fe GSEventRunModal + 65
26 UIKitCore 0x0000000119c7bba2 UIApplicationMain + 140
27 Sales Networker 0x000000010cca7f6b main + 75
28 libdyld.dylib 0x0000000112416541 start + 1
)
我测试了你的代码,发现这里发生了两件事。
首先是当您调用 requestAccess(for:completion:)
时状态已经是 .denied
,您会在控制台上得到一个 non-fatal 堆栈跟踪。您可以忽略此问题,也可以仅在 .notDetermined
状态下请求访问权限。
第二个问题与主队列上的同步调度有关。由于某种原因,这会导致访问冲突。解决方案是使用 asynchronous 调度。无论如何都没有充分的理由阻止调用队列。
func checkAccessStatus(_ completionHandler: @escaping (_ accessGranted: Bool) -> Void) {
let authorizationStatus = CNContactStore.authorizationStatus(for: .contacts)
switch authorizationStatus {
case .authorized:
completionHandler(true)
case .denied:
self.showSettingsAlert(completionHandler)
case .notDetermined:
store.requestAccess(for: .contacts, completionHandler: { (access, accessError) -> Void in
if access {
completionHandler(access)
}
else {
print("access denied")
self.showSettingsAlert(completionHandler)
}
})
default:
completionHandler(false)
}
}
private func showSettingsAlert(_ completionHandler: @escaping (_ accessGranted: Bool) -> Void) {
let msg = "This app requires access to Contacts to proceed. Would you like to open settings and grant permission?"
let alert = UIAlertController(title: nil, message: msg, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Open Settings", style: .default) { action in
completionHandler(false)
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { action in
completionHandler(false)
})
DispatchQueue.main.async {
if let vc = UIApplication.getPresentedViewController() {
vc.present(alert, animated: true)
} else {
completionHandler(false)
}
}
}
这个错误会在两种情况下出现。
当您向用户请求联系权限时,用户拒绝了。
用户已获得联系权限,稍后进入
settings -> your app > off contact permission
。
在日志中的这两种情况下,您将看到“承诺已完成,但出现零错误”。 根据客户要求,您可以显示权限被拒绝警报,也可以忽略它。