Hello,
We're seeing a high rate of providerDidReset callbacks in production across a large user base (iOS 16, 17, 18, and 26). I'd like to understand both the correct way to handle this delegate method and strategies to reduce its frequency.
Background
- The callback occurs across all iOS versions we support and is not isolated to a specific device or region.
- The callback can occur in any app state (
foreground,background,inactive), however it is most dominant in thebackgroundstate — particularly during VoIP push notification handling. - The callback is more prevalent during long app sessions — for example, when the app has been running continuously for a day or overnight.
- We do not call
CXProvider.invalidate()anywhere in our codebase explicitly. - After
providerDidResetfires, subsequent transactions fail withCXErrorCodeRequestTransactionErrorUnknownCallUUID(error code 4).
Re-initializing the provider via initializeProvider() resolves this error.
Our Implementation
We use a singleton proxy class (CallKitProxy) that owns the CXProvider.
Below is a simplified version — some logging and non-essential parts have been removed for brevity.
@objcMembers
public final class CallKitProxy: NSObject {
private var cxProvider: CXProvider?
private let cxCallController: CXCallController
private let cxCallObserver: CXCallObserver
private override init() {
cxCallObserver = CXCallObserver()
cxCallController = CXCallController()
super.init()
initializeProvider()
cxCallObserver.setDelegate(self, queue: nil)
}
private func initializeProvider() {
let configuration = providerConfiguration()
cxProvider = CXProvider(configuration: configuration)
cxProvider?.setDelegate(self, queue: nil)
}
private func providerConfiguration() -> CXProviderConfiguration {
let soundName = SharedUDHelper.shared.string(forKey: .pushNotificationSoundNameForCall)
let sound = CallNotificationSounds(name: soundName ?? "ringtoneDefault")
let configuration = CXProviderConfiguration()
configuration.supportsVideo = true
configuration.maximumCallsPerCallGroup = 1
configuration.maximumCallGroups = 1
configuration.supportedHandleTypes = [.phoneNumber, .generic]
configuration.iconTemplateImageData = UIImage(
named: "callkit_mask",
in: .main,
compatibleWith: nil
)?.pngData()
configuration.ringtoneSound = sound.name
return configuration
}
public func requestTransaction(
action: CXCallAction,
completion: @escaping (Error?) -> Void
) {
let transaction = CXTransaction(action: action)
cxCallController.request(transaction) { error in
completion(error)
}
}
}
extension CallKitProxy: CXProviderDelegate {
public func providerDidReset(_ provider: CXProvider) {
// End any active calls, then re-initialize the provider
initializeProvider()
}
}
Questions
1. Is re-initializing the provider inside providerDidReset the correct approach?
The documentation states that providerDidReset signals the provider has been reset and all calls should be considered terminated. Should we be calling CXProvider.invalidate() on the old instance before creating a new one? Or is assigning a new CXProvider to cxProvider (which releases the old instance) sufficient?
2. What could be causing providerDidReset to fire so frequently, and how can we reduce it?
We're particularly concerned about cases triggered during VoIP push handling in the background and inactive states. Are there known conditions — such as provider configuration changes, app lifecycle events, or system memory pressure — that commonly trigger this callback? And are there any recommended patterns to make the provider more resilient in these scenarios?
Thank you.