I have a possible solution, though the problem I saw was slightly different from what you described. Here's the quick version: use at least a .userInitiated
qualityOfService
on Catalyst.
Here's what I think is happening.
CKOperation.h provides this documentation:
@discussion CKOperations behave differently depending on how you set qualityOfService.
*
* @code
* Quality of Service | timeoutIntervalForResource | Network Error Behavior | Discretionary Behavior
* -------------------+----------------------------+------------------------+-----------------------
* UserInteractive | -1 (no enforcement) | fail | nonDiscretionary
* UserInitiated | -1 (no enforcement) | fail | nonDiscretionary
* Default | 1 week | fail | discretionary when app backgrounded
* Utility | 1 week | internally retried | discretionary when app backgrounded
* Background | 1 week | internally retried | discretionary
* @endcode
* timeoutIntervalForResource
* - the timeout interval for any network resources retrieved by this operation
* - this can be overridden via CKOperationConfiguration's timeoutIntervalForResource property
*
* Network Error Behavior
* - when a network request in service of a CKOperation fails due to a networking error, the operation may fail with that error, or internally retry the network request. Only a subset of networking errors are retried, and limiting factors such as timeoutIntervalForResource are still applicable.
*
* Discretionary Behavior
* - network requests in service of a CKOperation may be marked as discretionary
* - discretionary network requests are scheduled at the description of the system for optimal performance
*
* CKOperations have a default qualityOfService of Default.
Note that the Discretionary Behavior for Default is "discretionary when app backgrounded", and that the description of Discretionary Behavior says that "discretionary network requests are scheduled at the [discretion] of the system for optimal performance". I think that's what's happening in Catalyst; if the app isn't in the foreground, requests become discretionary — i.e., deferrable. And, in my case, the deferral seemed to be indefinite.
Here's what I was seeing.
- I launched the app on an iPhone
- I launched the app on a Mac
- I put the Mac app in the "background" (i.e., it was no longer active)
- I made a change on the iPhone
- The Mac received a remote notification
- In response to the remote notification the Mac tried to download (fetch) records
- The download request never completed
Here's what I did.
I was already assigning a CKOperationGroup
to all of my CKOperation
s, and I was already assigning a value to defaultConfiguration.qualityOfService
. On Catalyst I changed things so that the minimum qualityOfService
I assigned was .userInitiated
. That made the problem go away in testing. (To be clear, I tried many things; that was the one thing that worked.) I pushed a release out to production last night, and — so far at least — I no longer see the problem there either.
If you don't use CKOperationGroup
s, no worries, you can set qualityOfService
directly on the CKOperation
s; that should work equally well, it just isn't exactly what I did.
I filed a bug with Apple through Feedback.