CloudKit - CKQueryOperation with dependency

I'm just starting out with CloudKit, so bear with me.

Background Information

At WWDC 2015, the apple talked about CloudKit https://developer.apple.com/videos/wwdc/2015/?id=715

In this conversation, they warn about creating a query chain and instead recommend this tactic:

let firstFetch = CKFetchRecordsOperation(...) let secondFetch = CKFetchRecordsOperation(...) ... secondFetch.addDependency(firstFetch) letQueue = NSOperationQueue() queue.addOperations([firstFetch, secondFetch], waitUntilFinished: false) 

Structure example

The database of test projects contains pets and their owners, it looks like this:

 |Pets | |Owners | |-name | |-firstName | |-birthdate | |-lastName | |-owner (Reference) | | | 

My question

I am trying to find all the pets owned by the owner, and I worry that I am creating an apple chain warning of this. See below two methods that do the same, but two ways. Which is more correct or erroneous? I feel like I am doing the same, but instead use only completion blocks.

I am confused about how to change otherSearchBtnClick: use dependency. Where do i need to add

 ownerQueryOp.addDependency(queryOp) 

in otherSearchBtnClick :?

 @IBAction func searchBtnClick(sender: AnyObject) { var petString = "" let container = CKContainer.defaultContainer() let publicDatabase = container.publicCloudDatabase let privateDatabase = container.privateCloudDatabase let predicate = NSPredicate(format: "lastName == '\(ownerLastNameTxt.text)'") let ckQuery = CKQuery(recordType: "Owner", predicate: predicate) publicDatabase.performQuery(ckQuery, inZoneWithID: nil) { record, error in if error != nil { println(error.localizedDescription) } else { if record != nil { for owner in record { let myRecord = owner as! CKRecord let myReference = CKReference(record: myRecord, action: CKReferenceAction.None) let myPredicate = NSPredicate(format: "owner == %@", myReference) let petQuery = CKQuery(recordType: "Pet", predicate: myPredicate) publicDatabase.performQuery(petQuery, inZoneWithID: nil) { record, error in if error != nil { println(error.localizedDescription) } else { if record != nil { for pet in record { println(pet.objectForKey("name") as! String) } } } } } } } } } @IBAction func otherSearchBtnClick (sender: AnyObject) { let container = CKContainer.defaultContainer() let publicDatabase = container.publicCloudDatabase let privateDatabase = container.privateCloudDatabase let queue = NSOperationQueue() let petPredicate = NSPredicate(format: "lastName == '\(ownerLastNameTxt.text)'") let petQuery = CKQuery(recordType: "Owner", predicate: petPredicate) let queryOp = CKQueryOperation(query: petQuery) queryOp.recordFetchedBlock = { (record: CKRecord!) in println("recordFetchedBlock: \(record)") self.matchingOwners.append(record) } queryOp.queryCompletionBlock = { (cursor: CKQueryCursor!, error: NSError!) in if error != nil { println(error.localizedDescription) } else { println("queryCompletionBlock: \(cursor)") println("ALL RECORDS ARE: \(self.matchingOwners)") for owner in self.matchingOwners { let ownerReference = CKReference(record: owner, action: CKReferenceAction.None) let ownerPredicate = NSPredicate(format: "owner == %@", ownerReference) let ownerQuery = CKQuery(recordType: "Pet", predicate: ownerPredicate) let ownerQueryOp = CKQueryOperation(query: ownerQuery) ownerQueryOp.recordFetchedBlock = { (record: CKRecord!) in println("recordFetchedBlock (pet values): \(record)") self.matchingPets.append(record) } ownerQueryOp.queryCompletionBlock = { (cursor: CKQueryCursor!, error: NSError!) in if error != nil { println(error.localizedDescription) } else { println("queryCompletionBlock (pet values)") for pet in self.matchingPets { println(pet.objectForKey("name") as! String) } } } publicDatabase.addOperation(ownerQueryOp) } } } publicDatabase.addOperation(queryOp) } 
+6
source share
3 answers

in theory, you could have several owners and, therefore, several dependencies. In addition, internal queries will be created after the external query is completed. You will be late to create addiction. In your case, it is probably easier to force the execution of internal queries in a separate queue as follows:

 if record != nil { for owner in record { NSOperationQueue.mainQueue().addOperationWithBlock { 

This way, you will ensure that each internal request is executed in a new queue and at the same time that the parent request can complete.

Something else: to make your code cleaner, it would be better if all the code inside the for loop was in a separate function with CKReference as a parameter.

+1
source

If you don’t need a cancellation and don’t have to worry about retrying a network error, I think you are perfectly linking requests.

I know that I know, at WWDC 2015. Nihar Sharma recommended the addiction dependency approach, but it seems he just threw it at the end without hesitation. You see that it is impossible to repeat NSOperation, because they are nonetheless disposable, and he did not offer any example for canceling operations already in the queue or how to transfer data from one operation from the next. Given these 3 complications that can take you several weeks, just stick to what you have and wait for the next WWDC to resolve them. Plus, the entire block block allows you to call built-in methods and have access to parameters in the method described above, so if you go to operations, you usually do not get the full benefit of this benefit.

His main reason not to use the chain is funny that he couldn’t say what kind of error for which request, he had his error names someError, and then otherError, etc. None of their right names were wrong, just use the same name for all of them, and then you know that inside the block you always use the correct error. Thus, he was the one who created his messy script and proposed a solution for him, however the best solution just doesn't create a messy script of multiple error parameter names in the first place!

For all that you say, if you still want to try using dependencies, here is an example of how this can be done:

 __block CKRecord* venueRecord; CKRecordID* venueRecordID = [[CKRecordID alloc] initWithRecordName:@"4c31ee5416adc9282343c19c"]; CKFetchRecordsOperation* fetchVenue = [[CKFetchRecordsOperation alloc] initWithRecordIDs:@[venueRecordID]]; fetchVenue.database = [CKContainer defaultContainer].publicCloudDatabase; // init a fetch for the category, it just a placeholder just now to go in the operation queue and will be configured once we have the venue. CKFetchRecordsOperation* fetchCategory = [[CKFetchRecordsOperation alloc] init]; [fetchVenue setFetchRecordsCompletionBlock:^(NSDictionary<CKRecordID *,CKRecord *> * _Nullable recordsByRecordID, NSError * _Nullable error) { venueRecord = recordsByRecordID.allValues.firstObject; CKReference* ref = [venueRecord valueForKey:@"category"]; // configure the category fetch fetchCategory.recordIDs = @[ref.recordID]; fetchCategory.database = [CKContainer defaultContainer].publicCloudDatabase; }]; [fetchCategory setFetchRecordsCompletionBlock:^(NSDictionary<CKRecordID *,CKRecord *> * _Nullable recordsByRecordID, NSError * _Nullable error) { CKRecord* categoryRecord = recordsByRecordID.allValues.firstObject; // here we have a venue and a category so we could call a completion handler with both. }]; NSOperationQueue* queue = [[NSOperationQueue alloc] init]; [fetchCategory addDependency:fetchVenue]; [queue addOperations:@[fetchVenue, fetchCategory] waitUntilFinished:NO]; 

How it works, first he records a place, then he selects his category.

Sorry, there is no error handling, but since you can see that there have already been tons of code, something can be done in several lines with a chain. And personally, I find this result more confusing and confusing than just combining convenience methods.

+2
source

I had the same problem recently and as a result, I used NSBlockOperation to prepare the second request and added a dependency to make it all work:

  let container = CKContainer.defaultContainer() let publicDB = container.publicCloudDatabase let operationqueue = NSOperationQueue.mainQueue() let familyPredicate = NSPredicate(format: "name == %@", argumentArray: [familyName]) let familyQuery = CKQuery(recordType: "Familias", predicate: familyPredicate) let fetchFamilyRecordOp = CKQueryOperation(query: familyQuery) fetchFamilyRecordOp.recordFetchedBlock = { record in familyRecord = record } let fetchMembersOP = CKQueryOperation() // Once we have the familyRecord, we prepare the PersonsFetch let prepareFamilyRef = NSBlockOperation() { let familyRef = CKReference(record: familyRecord!, action: CKReferenceAction.None) let familyRecordID = familyRef?.recordID let membersPredicate = NSPredicate(format: "familia == %@", argumentArray: [familyRecordID!]) let membersQuery = CKQuery(recordType: "Personas", predicate: membersPredicate) fetchMembersOP.query = membersQuery } prepareFamilyRef.addDependency(fetchFamilyRecordOp) fetchMembersOP.recordFetchedBlock = { record in members.append(record) } fetchMembersOP.addDependency(prepareFamilyRef) fetchMembersOP.database = publicDB fetchFamilyRecordOp.database = publicDB operationqueue.addOperations([fetchFamilyRecordOp, fetchMembersOP, prepareFamilyRef], waitUntilFinished: false) 

And now it works as I expected, because you can configure your operations in a very granular way and execute them in the correct order ^. ^

in your case, I would structure it as follows:

 let predicate = NSPredicate(format: "lastName == '\(ownerLastNameTxt.text)'") let ckQuery = CKQuery(recordType: "Owner", predicate: predicate) let getOwnerOperation = CKQueryOperation(query: ckQuery) getOwnerOperation.recordFetchedBlock = { record in let name = record.valueForKey("name") as! String if name == myOwnerName { ownerRecord = record } } //now we have and operation that will save in our var OwnerRecord the record that is exactly our owner //now we create another that will fetch our pets let queryPetsForOurOwner = CKQueryOperation() queryPetsForOurOwner.recordFetchedBlock = { record in results.append(record) } //That all this op has to do, BUT it needs the owner operation to be completed first, but not inmediately, we need to prepare it query first so: var fetchPetsQuery : CKQuery? let preparePetsForOwnerQuery = NSBlockOperation() { let myOwnerRecord = ownerRecord! let ownerRef = CKReference(record: myOwnerRecord, action: CKReferenceAction.None) let myPredicate = NSPredicate(format: "owner == %@", myReference) fetchPetsQuery = CKQuery(recordType: "Pet", predicate: myPredicate) } queryPetsForOurOwner.query = fetchPetsQuery preparePetsForOwnerQuery.addDependency(getOwnerOperation) queryPetsForOurOwner.addDependency(preparePetsForOwnerQuery) 

and now all you have to do is add them to the newly created operation queue after we send them to our database

 getOwnerOperation.database = publicDB queryPetsForOurOwner.database = publicDB let operationqueue = NSOperationQueue.mainQueue() operationqueue.addOperations([getOwnerOperation, queryPetsForOurOwner, preparePetsForOwnerQuery], waitUntilFinished: false) 

PS: I know that I said Family and Person, and the names are different, but I’m Spanish and I’m testing some operations with the cloud type, so I haven’t standardized the names in Russian yet;)

0
source

All Articles