CloudKit Operations
CloudKit’s closure-based Convenience API is great for quick or simple tasks, but when you try to do anything complex, you can end up in a recursive nightmare of closures nested within closures nested within closures.
Operations
Instead of using CloudKit’s Convenience API, you can use operations instead. CloudKit has many different operation classes which inherit from NSOperation and bring all of the same benefits. Operations let you write cleaner code that encapsulate individual pieces of work. With operations, you can set dependencies, priority, and quality of service to better manage how and when your code gets run. Another large benefit of operations is the ability to cancel them, something that is much harder with closures. For more on why you’d want to be using operations, I’d highly recommend this WWDC talk (text).
CKOperation
CloudKit operations are all subclasses of the abstract class CKOperation. Like other NSOperations, CKOperations can be added to an NSOperationQueue, but they can also be added to a CKContainer. CKOperations have a container property, that if left as nil will default to the app’s default container. If you add the operation to a CKContainer instead of an NSOperationQueue, it will set container for you.
CKDatabaseOperation
CKOperation has a subclass CKDatabaseOperation that is also an abstract class, but for operations that can be added to a specific CKDatabase. Similar to CKOperation’s container property, there is a database property that is set for you if you add the operation to a CKDatabase. If added to an operation queue and this property isn’t set, it defaults to the private database of the app’s default container.
Benefits
CKOperations provide more benefits over the Convenience API than just clean code and the ability to cancel.
Work with many records at once
With CloudKit’s Convenience API you can save, fetch or delete individual records. With operations, you can fetch multiple records at once using CKFetchRecordsOperation.
And you can save and delete multiple records at once using CKModifyRecordsOperation.
For these and some other records based CKDatabaseOperation classes, you also get perRecordCompletionBlock and perRecordProgressBlock properties that can be very helpful.
GET more records
CKDatabase has a convenience method for performing a query, but per Apple’s documentation:
Do not use this method when the number of returned records is potentially more than a few hundred records. For efficiency, all queries automatically limit the number of returned records based on current conditions. If your query hits the maximum value, this method returns only the first portion of the overall results.
To get all records matching a query, use CKQueryOperation which can return all of the records to you in batches. Each record is returned to you in a recordFetchedBlock and then in the operation’s queryCompletionBlock you might be given a CKQueryCursor that you can use to make a new CKQueryOperation for the next batch of records.
Limit how much data you retrieve
CKDatabaseOperation classes that retrieve records have a property desiredKeys which limits which values of the retrieved records are returned. I’ve found this to be particularly useful in working with records that have assets that you don’t want to download multiple times or record types with many fields.
Long-Lived Operations!
With iOS 9.3, Apple is adding the ability for CKOperations to continue running, even if the app exits. Setting up a long-lived operation is pretty simple, just set the operation’s longLived property to true.
When the operation is saved outside of the app, the operation’s longLivedOperationWasPersistedBlock is called. If the app terminates before this block is called, the operation was not persisted. If the operation completes while the app is still active, the completion block is called like normal.
If the app terminates, the operation will continue to run, and the system will cache the response if the operation completes. When the app is launched again, you can retrieve all existing long-lived operation IDs, and fetch the operations using these IDs.
After getting each operation, you can cast the operation to the correct CKOperation subclass and set its completion block again. When you are finished configuring the operation, add it to the CKDatabase again. If the system has a cached response, it will call the completion blocks right away. If the operation is still running, the completion blocks will run after the operation finishes like usual.
TL;DR
For quick or simple CloudKit tasks, the Convenience API works great. But CKOperations give you more power and flexibility, while still being quick and easy to use, and should be used in most cases.