Search code examples
iosswiftcore-dataicloudcloudkit

How to delete a CKRecord if not yet finished creating due to bad network connection?


I am currently working on an iPhone App which utilizes CloudKit for syncing the app data between the users devices. I use CoreData as my local cache to make sure that the app stays usable when the device is offline.

The Problem

During the development I came across an issue concerning the sync behavior when the device is offline. Let me explain by giving an example:

  1. The user creates a Person (one entity I'm dealing with)
  2. The person is saved to the local cache → CoreData
  3. A CKRecord is created and set up to match the data of the locally cached entity
  4. To save the CKRecord to CloudKit, a CKModifyRecordsOperation is created and set up with all the completion blocks and properties needed
  5. The CKModifyRecordsOperation is added to the database

If the device has a working network connection the CKRecord is created as desired. But when the operation fails due to a bad network connection the CKRecord is not created.

Let's assume the device stays offline and the user decides to delete the person again. This is no problem for the data locally cached on the device. But due to the fact that the local cache has no CKRecordID associated, no CKModifyRecordsOperation can be created to delete the CKRecord in the cloud.

Now the device establishes a network connection and is online again. So now the CKModifyRecordsOperation to create the Person-Entity is executed. This results in the local cache and the cloud being out of sync.

I thought of fixing this issue by keeping track of pending operations concerning a Person-Entity. If the person gets deleted the pending operations get cancelled.

Unfortunately I could not get this running. So I would appreciate some advice if I'm on the right track!

Thank you!


Solution

  • Try adjusting the .qualityOfService attribute on the operation. Per Apple Docs at https://developer.apple.com/library/content/documentation/Performance/Conceptual/EnergyGuide-iOS/PrioritizeWorkWithQoS.html#//apple_ref/doc/uid/TP40015243-CH39:

    User-interactive: Work that is interacting with the user, such as operating on the main thread, refreshing the user interface, or performing animations. If the work doesn’t happen quickly, the user interface may appear frozen. Focuses on responsiveness and performance. Work is virtually instantaneous.

    User-initiated Work that the user has initiated and requires immediate results, such as opening a saved document or performing an action when the user clicks something in the user interface. The work is required in order to continue user interaction. Focuses on responsiveness and performance. Work is nearly instantaneous, such as a few seconds or less.

    Utility Work that may take some time to complete and doesn’t require an immediate result, such as downloading or importing data. Utility tasks typically have a progress bar that is visible to the user. Focuses on providing a balance between responsiveness, performance, and energy efficiency. Work takes a few seconds to a few minutes.

    Background Work that operates in the background and isn’t visible to the user, such as indexing, synchronizing, and backups. Focuses on energy efficiency. Work takes significant time, such as minutes or hours.

    The page also says the default is between user-initiated and Utility.

    According to this discussion on the Apple dev forums https://forums.developer.apple.com/thread/20047, users reported not receiving errors for queries that failed while being offline and, like you're seeing, it seems those queries were actually persisted and tried again when connection was restored. Users in that thread reported that changing the QoS parameter to User-initiated caused an error to be returned immediately when the operation couldn't be completed due to no network.

    It also appears that when the operation is being persisted, the operation's longLivedOperationWasPersistedBlock will be called.

    So, option 1: try adjusting the QoS value to a "higher" (more urgent) value on the operation, which should cause errors to be thrown rather than queuing the operation for later.

    Option 2: try adding the longLivedOperationWasPersistedBlock. If it fires, you could try canceling the operation in that block, and displaying a "no network" error to the user.