CKQuery slow with CKAssets - ios

When I fetch for records with fields that are Strings (or others) it takes under 1 second:
let query = CKQuery(recordType: "Messages", predicate: NSPredicate(format: "TRUEPREDICATE"))
NSLog("Started fetching")
self.publicDb.performQuery(query, inZoneWithID: nil) { (records, error) in
NSLog("Finished fetching")
print(records!.count)
}
But, when this record type contains a CKAsset, the time for the query goes up to at least 3 seconds. This is not acceptable when I want to load an image that is 100kb. It's the same when I put the asset in as a reference and load it from there. What can I do to speed up queries for records that contain assets or is there any other way to store assets in a more efficient way?

Use an operation and set the quality of service:
CKQueryOperation *queryOperation = [[CKQueryOperation alloc] initWithQuery:query];
queryOperation.qualityOfService = NSQualityOfServiceUserInitiated;
If you don't set qualityOfService, you will get the background level and it will be slow.

Related

Unable to fetch records in a sharedCloudDatabase custom Zone using CloudKit

I am trying to fetch CloudKit records from a custom Zone in a sharedDatabase.
The zone has been created correctly during the share process. So I assume that the zone is correctly a shared custom zone (it is indeed in my CloudKit user dashboard, appearing under the sharedDatabase of the default container).
Even with this simple piece of code to retrieve records:
func loadRecords() {
let database = CKContainer.default().sharedCloudDatabase
let query = CKQuery(recordType: "Items", predicate: NSPredicate(format: "TRUEPREDICATE", argumentArray: nil))
let operation = CKQueryOperation(query: query)
let zone = CKRecordZone(zoneName: "MyCustomZone")
var fetchedRecords: [CKRecord] = []
database.perform(query, inZoneWith: zone.zoneID) { (records, error) in
if let error = error {
print(error)
}
else if let records = records, let firstRecord = records.first {
print(firstRecord)
}
}
}
I keep receiving this error: (sorry was missing in previous post!)
<CKError 0x280950840: "Invalid Arguments" (12/1009); "Only shared zones can be accessed in the shared DB">
Any idea about what I am missing? Thank you!
Right, I finally figured it out. Thanks to this additional post here on StackOverflow: CloudKit Sharing
You indeed have to fetch your custom zone. But it is not enough to give it a name such as "MyCustomZone" because in your shared DB, it becomes MyCustomZone: _acBxxxyyyzzz. A suffix added by CloudKit.
If you don't fetch all your shared zones, you never have the right custom zone from which you need to pickup the record.

Query with compare predicate in CloudKit

I have some CKRecords in Cloudkit, the records has two property of int type, which may be called 'cost' and 'price', I want to query records whose 'cost' > 'price', but the application crashed when I write a query like this:
CKQuery *query = [[CKQuery alloc] initWithRecordType:#"MyRecordType" predicate:
[NSPredicate predicateWithFormat:#"cost > price"]];
this is the crash info given by Xcode:
Terminating app due to uncaught exception 'CKException', reason: 'Invalid predicate, Invalid right expression, is not a function expression
Please Help, thanks in advance.
OK,
Thought some more about this.
You query all records, but select only two fields; the cost and the price.
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Whatever", predicate: predicate)
let operation = CKQueryOperation(query: query)
operation.desiredKeys = ["cost","price"]
Then you do your query [which should be quicker], sort out the records you want and then go get them with publicDB.fetchRecordWithID.
Yes, I know more than one query, in fact it looks like a lot more, but wait I think there is an answers in WWDC 2015 Tips & Tricks in Cloudkit; Watch the session and you might find something in there too. Here the code to fetch a record, if you have the record ID.
publicDB.fetchRecordWithID(), completionHandler: {record, error in
if error != nil {
println("there was an error \(error)")
} else {
NSOperationQueue.mainQueue().addOperationWithBlock {
self.plasticOne.text = (record.objectForKey("subCategory") as String)
}
}
})
Sorry I cannot give you more than that without writing the code myself, and if I do that than you end up using my answer, and I sure you're answer is going to be better :)

Powering Master-Detail with CloudKit

I have a UISearchResultsController that searches locally while typing, and remotely (CloudKit) when the search button is pressed. The number of results returned needs to be 10-30 (I'm currently testing my app with 25 and it's always enough)
The search results list is populated with RecordType1, while it's detail is populated by RecordType1 and RecordType2. My question is how to go about fetching the second reference type while minimizing my requests/sec. I was looking at Apple's CloudCaptions sample, and they solve the problem by fetching the second record type when each of RecordType1 is fetched. It seems that this needlessly creates fetch requests (1[RecordType1] + 25[RecordType2] = 26 requests). How can I reduce this? It seems like it should be possible in two requests (one for RecordType1 and then one to fetch all the RecordType2 associated with it).
UPDATE: RecordType2 has a back reference to RecordType1
Unless I am misunderstanding your problem, I think you can just execute a query on your CloudKit database:
let searchKey = ... // value for recordType1
let z = CKRecordZone.default()
let predicate = NSPredicate(format: "recordType1 == %#", searchKey)
let query = CKQuery(recordType: "recordType2", predicate: predicate)
db.perform(query, inZoneWith: z.zoneID) { (records, error) in
if error != nil {
// `records` contains recordType2
} else {
// check for errors
}
}
You can also search for a multiple keys using the IN comparison in the predicate:
let searchKeys = ... // [value1, value2, value3, etc.]
let predicate = NSPredicate(format: "recordType1 IN %#", searchKeys)
References
CloudKit CKQueryOperation (Apple)
CloudKit CKQuery (Apple)

Code pattern / snippet for retrieving single column from cloudkit

I'm building an app which allows users to upload / download info from a common store. I thought cloudKit storage would be ideal for this.
I'd like for users to be able to search records in the store by a KeyWord field, then download the entire record when they select one.
In SQL terms this would be like:
SELECT id,KeyWords from myDB WHERE KeyWords LIKE %searchstring%
Followed by:
SELECT * FROM myDB WHERE id = selectedID
I have been using this code pattern to retrieve records from cloudKit storage:
var publicDatabase: CKDatabase?
let container = CKContainer.defaultContainer()
override func viewDidLoad() {
super.viewDidLoad()
publicDatabase = container.publicCloudDatabase
}
func performQuery(){
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Library", predicate: predicate)
publicDatabase?.performQuery(query, inZoneWithID: nil,
completionHandler: ({results, error in
...
[code to handle results / error here]
...
})
}
but this returns all the content of each record which is a lot of unnecessary info.
I'd like to only fetch a single field from the cloudkit records.
Is there an easy way to do this and does anyone have a code snippet?
CKFetchRecordsOperation allows downloading of restricted columns but requires that you know the ids of the records to download.
For this you can use the desiredKeys property on a CKQueryOperation. For more information see: desiredkeys documentation
OK Got something working. This may well be hacky but I put it up for info for anyone else in the same situation...
let predicate = NSPredicate(value: true) // returns all - replace with whatever condition you want
let query = CKQuery(recordType: "Library", predicate: predicate) // create a query using the predicate
var operation = CKQueryOperation(query: query) // create an operation using the query
operation.desiredKeys = ["KeyWords"] // Array of whatever 'columns' you want to return
// operation.resultsLimit = 15 // optional limit on records
// Define a closure for what to do for each returned record
operation.recordFetchedBlock = { [weak self] (record:CKRecord!) in
// Do whatever you want with each returned record
println(record.objectForKey("KeyWords"))
}
// Define a closure for when the operation is complete
operation.queryCompletionBlock = { [weak self] (cursor:CKQueryCursor!, error:NSError!) in
if cursor != nil {
// returns the point where the operation left off if you want t retrieve the rest of the records
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// Do what you want when process complete
self!.tableView.reloadData()
if error != nil {
println("there was an error")
}
})
}
self.publicDatabase!.addOperation(operation) // Perform the operation on given database

Fetch All CKRecords of certain type

Let's say I have records of type A in the private CloudKit database. How do I fetch ALL records of that type, without specifying record IDs or a query predicate?
CKQuery requires a non-nil predicate on initialization (see documentation), so you have to specify a predicate, even if it's always true. Ex:
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "TheRecordType", predicate: predicate)
let operation = CKQueryOperation(query: query)
operation.queryCompletionBlock = {cursor, error in
// done
}
CKContainer.defaultContainer().privateCloudDatabase.addOperation(operation)

Resources