How to Queue Up Lots of Inbound CloudKit Notifications

118 views Asked by At

Let's say I save 50 records to CloudKit with a CKModifyRecordsOperation like this:

let operation = CKModifyRecordsOperation(recordsToSave: records, recordIDsToDelete: nil)
operation.savePolicy = .changedKeys

operation.modifyRecordsCompletionBlock = { records, _, error in
    //...
}

privateDB.add(operation)

On my other devices, I get sprayed with 50 different background notifications for each CKRecord that changed. That's fine, I expect that.

I process each inbound notification like this:

func processCloudKitNotification(notification: CKNotification, database: CKDatabase){

  guard let notification = notification as? CKQueryNotification else { return }

  if let recordID = notification.recordID{
    var recordIDs = [CKRecordID]()

    switch notification.queryNotificationReason {
      case .recordDeleted:
        //Delete [-]
        //...
      default:
        //Add and Edit [+ /]
        recordIDs.append(recordID)
    }

    let fetchOperation = CKFetchRecordsOperation(recordIDs: recordIDs)

    fetchOperation.fetchRecordsCompletionBlock = { records, error in
      //...
    }

    database.add(fetchOperation)
  }
}

But since each of the 50 incoming notifications are separate events, this function gets called 50 different times, thus triggering a slew of requests to pull down the full records using the CKRecordIDs that the notifications give me.

How can I queue up all the incoming notification CKRecordIDs within a reasonable period of time so that I can make a more efficient CKFetchRecordsOperation request with more than one recordID at a time?

1

There are 1 answers

0
Clifton Labrum On

I ended up using a timer for this, and it works quite well. Basically I start a timer when a new push notification comes in, and I reset it each time an additional notification arrives. Meanwhile, I collect all the CKRecordID's that are coming in and then process them when the timer fires (which happens after the notifications stop flowing in).

Here's my code:

var collectNotificationsTimer: Timer?
var recordIDsFromNotifications = [CKRecordID]()

func processCloudKitNotification(notification: CKNotification, database: CKDatabase){

  guard let notification = notification as? CKQueryNotification else { return }
  guard let recordID = notification.recordID else { return }

  //:: 1 :: Collect record IDs from incoming notifications
  if notification.queryNotificationReason == .recordDeleted{
    //Delete [-]
    //...
  }else{
    //Add and Edit [+ /]
    recordIDsFromNotifications.append(recordID)

    //:: 2 :: After collecting IDs for 1 second, request a batch of updated records
    collectNotificationsTimer?.invalidate()
    collectNotificationsTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false){ _ in

      let fetchOperation = CKFetchRecordsOperation(recordIDs: recordIDsFromNotifications)

      fetchOperation.fetchRecordsCompletionBlock = { records, error in
        recordIDsFromNotifications.removeAll()

        if let error = error {
          checkCloudKitErrorAndRetryRequest(name: "fetchRecordsCompletionBlock", error: error){}
        }else{
          if let records = records{
            //Save records...
          }
        }
      }
      database.add(fetchOperation)
    }
  }
}