CloudKit – 具有依赖性的CKQueryOperation
我刚刚开始与CloudKit合作,请耐心等待。
背景信息
在WWDC 2015上,苹果发表了一个关于CloudKit的演讲https://developer.apple.com/videos/wwdc/2015/?id=715
在这个谈话中,他们警告不要创build链式查询,而是推荐这种策略:
let firstFetch = CKFetchRecordsOperation(...) let secondFetch = CKFetchRecordsOperation(...) ... secondFetch.addDependency(firstFetch) letQueue = NSOperationQueue() queue.addOperations([firstFetch, secondFetch], waitUntilFinished: false)
示例结构
testing项目数据库包含宠物及其所有者,如下所示:
|Pets | |Owners | |-name | |-firstName | |-birthdate | |-lastName | |-owner (Reference) | | |
我的问题
我试图find属于一个所有者的所有宠物,我担心我正在创build链苹果警告反对。 请参阅下面的两个方法来做同样的事情,但有两种方法。 哪个更正确或者都是错误的? 我觉得我正在做同样的事情,但只是使用完成块。
我很困惑如何改变otherSearchBtnClick:使用依赖。 我需要在哪里添加
ownerQueryOp.addDependency(queryOp)
在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) }
理论上你可以有多个所有者,因此有多个依赖关系。 此外,内部查询将在外部查询已经执行之后创build。 你将不能创build一个依赖关系。 在你的情况下,强制执行内部查询可能更容易,如下所示:
if record != nil { for owner in record { NSOperationQueue.mainQueue().addOperationWithBlock {
这样你将确保每个内部查询将在一个新的队列上执行,同时父查询可以完成。
其他的东西:为了让你的代码更清洁,如果for循环中的所有代码都是以CKReference作为参数的独立函数,那将会更好。
如果你不需要取消,并不打扰重试networking错误,那么我认为你很好链接查询。
我知道,我知道,在WWDC 2015 Nihar Sharmabuild议添加依赖的方法,但似乎他只是扔在最后没有太多的想法。 你看到不可能重试一个NSOperation,因为它是一次性的,而且他没有提供任何取消已经在队列中的操作的例子,或者如何从下一个操作中传递数据。 鉴于这三个问题可能需要数周才能解决,只需坚持你的工作,并等待下一个WWDC的解决scheme。 再加上块的全部是让你调用内联方法,并能够在上面的方法访问参数,所以如果你转移到操作,你不能充分利用这种好处。
他不使用链接的主要原因是荒谬的,他不知道哪个错误是哪个请求,他的错误someError然后otherError等名称。没有人在他们的正确思想名称错误参数不同的块内,所以只是使用同名的所有人,然后你知道在一个块内,你总是使用正确的错误。 因此,他创build了他的混乱场景并提供了解决scheme,但是最好的解决scheme就是不要创build多个错误参数名称的混乱场景!
尽pipe如此,如果你仍然想尝试使用操作依赖关系,这里是一个如何完成的例子:
__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's 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];
它是如何工作的,首先是审查Venuelogging,然后获取它的类别。
对不起,没有error handling,但正如你所看到的,已经有很多代码可以通过链接来完成。 就我个人而言,我觉得这个结果比简单地把简便的方法联系起来更加复杂和混乱。
我最近有同样的问题,并最终使用NSBlockOperation准备第二个查询,并添加一个依赖项,使其全部工作:
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)
现在它按照我的预期工作了,因为你可以以非常精细的方式设置你的操作,并按照正确的顺序执行^ ^ ^
在你的情况下,我会像这样构造它:
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's all this op has to do, BUT it needs the owner operation to be completed first, but not inmediately, we need to prepare it's 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)
现在所有需要完成的工作就是将它们引导到我们的数据库之后,将它们添加到新创build的操作队列中
getOwnerOperation.database = publicDB queryPetsForOurOwner.database = publicDB let operationqueue = NSOperationQueue.mainQueue() operationqueue.addOperations([getOwnerOperation, queryPetsForOurOwner, preparePetsForOwnerQuery], waitUntilFinished: false)
PS:我知道我说的家庭和个人,名称不是这样,但我西class牙语和testing一些云端操作,所以我还没有标准化为英文loggingtypes的名字呢;)