CallKit CXCallObserver.calls reports stale `hasEnded` status

Hello Apple Developer Support Team,

I am experiencing an issue with the CallKit framework on iOS where the CXCallObserver.calls property does not accurately reflect the active call status in real-time.

The Issue:

Even when there are no active CallKit calls (all calls have been hung up), accessing callObserver.calls still returns CXCall objects where hasEnded is false. This leads to false positives when we check:

callObserver.calls.contains { !$0.hasEnded }

Our Setup:

We maintain a strong reference to CXCallObserver inside a singleton manager. We have assigned the CXCallObserverDelegate and it is receiving events. We noticed a significant delay (sometimes permanently on simulator/certain devices) between the call actually ending on the system level and the calls array being cleared. Questions:

Is the CXCallObserver.calls array designed to be an asynchronous snapshot, or should it guarantee real-time consistency with the actual system call state? Is this a known caching issue with callservicesd where ended calls are not immediately deallocated from the observer’s calls array? What is the recommended best practice for determining if there is an active call on the device without relying on the potentially stale callObserver.calls property? Thank you for your support, and I look forward to your guidance.

Best regards,

Is the CXCallObserver.calls array designed to be an asynchronous snapshot, or should it guarantee real-time consistency with the actual system call state?

That depends on your definition of "real-time". CXCallObserver is updating itself based on messages it receives from callservicesd. That introduces a small amount of latency from message overhead, but the bigger issue is probably a difference in how the interface behaves vs. the API. I haven't looked at this in many years, but my recollection is that while the UI update "immediately" (which it clearly does), the CXCall itself doesn't "end" until the controlling app fulfills the end action. In most cases, I'd expect that delay to be minimal, but that could be longer depending on the app’s behavior.

Two notes here:

We noticed a significant delay (sometimes permanently on simulator/certain devices)

  1. I consider the simulator inherently unreliable for this kind of testing. It may or may not work (I honestly haven't tried in a while), and you're welcome to file bugs, but you shouldn't be designing your architecture around trying to make the simulator "work".

  2. On the individual device case, my main concern here is that I'm not sure how it will behave if/when things break (for example, callservicesd crashes) down while your app is in the background. I think my suggestion here would be that you destroy and recreate CXCallObserver anytime your app returns to the foreground and/or you've been suspended for a long time or if CXProvider resets. I'm not sure that’s absolutely necessary, but it's easy enough and low risk.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

CallKit CXCallObserver.calls reports stale `hasEnded` status
 
 
Q