After upgrading to iOS beta 13 I've noticed an unpleasant thing: my app crashes sometimes on incoming VoIP pushes.
In the crash report I see the following:
iOS 13 Killing app because it never posted an incoming call to the system after receiving a PushKit VoIP callback
Fatal Exception: NSInternalInconsistencyException
0 CoreFoundation 0x1af21b9f0 __exceptionPreprocess
1 libobjc.A.dylib 0x1af7284fc objc_exception_throw
2 CoreFoundation 0x1af11efec +
[_CFXNotificationTokenRegistration keyCallbacks]
3 Foundation 0x1aeda1330 -[NSAssertionHandler
handleFailureInMethod:object:file:lineNumber:description:]
4 PushKit 0x19caa6b54 -[PKPushRegistry
_terminateAppIfThereAreUnhandledVoIPPushes]
5 libdispatch.dylib 0x1afa441ec _dispatch_client_callout
6 libdispatch.dylib 0x1af9f6c6c
_dispatch_lane_barrier_sync_invoke_and_complete
7 PushKit 0x19caa5b74 __73-[PKPushRegistry
voipPayloadReceived:mustPostCall:withCompletionHandler:]_block_invoke
8 libdispatch.dylib 0x1afa43678
_dispatch_call_block_and_release
9 libdispatch.dylib 0x1afa441ec
_dispatch_client_callout
10 libdispatch.dylib 0x1af9f61f8
_dispatch_main_queue_callback_4CF$VARIANT$mp
11 CoreFoundation 0x1af1992a0
CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
12 CoreFoundation 0x1af1942a8 __CFRunLoopRun
13 CoreFoundation 0x1af1937ac CFRunLoopRunSpecific
14 GraphicsServices 0x1ae395180 GSEventRunModal
15 UIKitCore 0x1b6e60244 UIApplicationMain
16 VOIPProject 0x1009822d8 main + 25
(AppDelegate.swift:25)
17 libdyld.dylib 0x1af6e9e7c start
I can not understand how to fix the problem. Am I obliged to post CallKit incoming call screen whenever I receive a VoIP push? It sounds crazy because I check if the notification is valid before showing the screen with incoming call. Can anybody explain what should I do?
On this thread from apple forums, someone from apple staff explained this:
On iOS 13.0 and later, incoming Voice over IP calls must be reported when they are received and before the didReceiceIncomingPush() method finishes execution, using the CallKit framework, or the system will terminate your app.
Repeatedly failing to report calls may prevent your app from receiving any more incoming call notifications.
Basically, you can no longer use VoIP pushes for non VoIP messaging, and will need to use regular push notifications for those.
This was announced during the WWDC session "Advances in App Background Execution" https://developer.apple.com/videos/play/wwdc2019/707/
I've been searching for answers on how to adapt an app for this change, and what I could gather is the following:
Voip Pushes
When your app receive this kind of push, it will need to report a new incoming call using CallKit. Therefore, this kind of push will be exclusive for calls that use CallKit.
It's recommended that you set the notification's apns-expiration
to 0, so you won't receive a push and be forced to present a call screen for a call that already expired.
Push Notifications
Regular push notifications are another option. If your server has all the information you need to write the notification text, you can send notifications that won't even run your app in the background. If you need to modify the content of the notification before presenting it to the user, you can use a Notification Service app extension, and if you need your app to be woken up and execute something in background, you can send silent push notifications.
Notification Service App Extension
To use this, you must set your notification's mutable-content
to 1. This way, your extension will receive the notification before it is presented to the user, allowing you to change its content, with a 30 seconds time limit.
The cons are that your app will stay in the background, only your extension will be allowed to run. This might mean that you will need to share information and code between your app and the extension, either by using user defaults, keychain, or by sharing your entire database (which might not be a simple task if your app is not prepared for that).
Silent Push Notifications
To send silent push notifications, you must set your notification's content-available
to 1 and remove it's alert, badge and sound. This notification will wake up your app in the background, and call your app delegate's didReceiveRemoteNotification
.
The downsides are quite annoying for this option:
apns-priority
of 5, which might cause them to be grouped and delivered in bursts, and even throttled or not delivered.I've faced same crash when tried using async version of didReceiveIncomingPushWith
. The code I've used:
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) async {
guard type == .voIP else { return }
let callUpdate = CXCallUpdate()
let phoneNumber = CXHandle(type: .phoneNumber, value: "handle")
callUpdate.remoteHandle = phoneNumber
try? await callProvider.reportNewIncomingCall(with: UUID(), update: callUpdate)
}
It works fine when the app is in foreground, but is crashing in background. Logs show that for some reason it's not getting called at all.
At the same time completion version works fine:
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
guard type == .voIP else { return }
let callUpdate = CXCallUpdate()
let phoneNumber = CXHandle(type: .phoneNumber, value: "handle")
callUpdate.remoteHandle = phoneNumber
callProvider.reportNewIncomingCall(with: UUID(), update: callUpdate) { error in
completion()
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With