CloudKit - CKQueryOperation с зависимостью

Я только начинаю работать с CloudKit, так что несите меня.

Фоновая информация

На WWDC 2015 яблоко рассказало о CloudKit https://developer.apple.com/videos/wwdc/2015/?id=715

В этом разговоре они предупреждают о создании цепочки запросов и вместо этого рекомендуют эту тактику:

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

Пример структуры

База данных тестовых проектов содержит домашних животных и их владельцев, она выглядит так:

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

Мой вопрос

Я пытаюсь найти всех домашних животных, принадлежащих владельцу, и я волнуюсь, что создаю цепочку apple, предупреждающую об этом. См. Ниже два метода, которые делают то же самое, но два пути. Что является более правильным или ошибочным? Я чувствую, что делаю то же самое, но вместо этого использую только блоки завершения.

Я смущен тем, как изменить otherSearchBtnClick: использовать зависимость. Где мне нужно добавить

ownerQueryOp.addDependency(queryOp)

в otherSearchBtnНажмите:?

@******** 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)
 }
 }
 }
 }
 }
 }
 }
 }
}
@******** 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)
}
3 ответа

в теории вы могли бы иметь несколько владельцев и, следовательно, несколько зависимостей. Кроме того, внутренние запросы будут созданы после выполнения внешнего запроса. Вы опоздаете, чтобы создать зависимость. В вашем случае, вероятно, проще принудительно выполнить выполнение внутренних запросов в отдельной очереди следующим образом:

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

Таким образом, вы убедитесь, что каждый внутренний запрос будет выполнен в новой очереди и в то время, когда родительский запрос может завершиться.

Что-то еще: чтобы сделать ваш код более чистым, было бы лучше, если бы весь код внутри цикла for был в отдельной функции с CKReference в качестве параметра.


Если вам не нужна аннулирование и не надо беспокоиться о повторной попытке сетевой ошибки, я думаю, что вы прекрасно связываете запросы.

Я знаю, что знаю, в WWDC 2015. Нихар Шарма рекомендовал подход зависимости зависимости, но, похоже, он просто бросил это в конце, не задумываясь. Вы видите, что повторить NSOperation невозможно, потому что они все равно одноразовые, и он не предложил никакого примера для отмены операций уже в очереди или как передать данные из одной операции из следующей. Учитывая эти 3 осложнения, которые могут занять у вас несколько недель, просто придерживайтесь того, что у вас есть, и ждите следующего WWDC для их решения. Плюс весь блок блоков позволяет вам вызывать встроенные методы и иметь доступ к параметрам в вышеописанном методе, поэтому, если вы перейдете к операциям, вы, как правило, не получаете полного преимущества этой выгоды.

Его главная причина не использовать цепочку - это смешно, что он не мог сказать, какая ошибка для какого запроса, у него были имена его ошибок someError, а затем otherError и т.д. Никто из их правных имен не ошибался, просто используйте одно и то же имя для всех из них, а затем вы знаете, что внутри блока вы всегда используете правильную ошибку. Таким образом, он был тем, кто создал свой беспорядочный сценарий и предложил решение для него, однако лучшее решение просто не создает беспорядочный сценарий множественных имен параметров ошибок в первую очередь!

При всем том, что вы говорите, если вы все еще хотите попробовать использовать зависимости, вот пример того, как это можно сделать:

__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];
</ckrecordid></ckrecordid>

Как это работает, сначала он записывает запись о месте, затем он выбирает свою категорию.

Извините, нет обработки ошибок, но, поскольку вы можете видеть, что уже было тонны кода, что-то может быть сделано в нескольких строках с цепочкой. И лично я считаю этот результат более запутанным и запутанным, чем просто объединение методов удобства.


У меня была такая же проблема в последнее время и в результате я использовал 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 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)

и теперь все, что нужно сделать, это добавить их во вновь созданную очередь операций после того, как мы направим их в нашу базу данных

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

P.S: Я знаю, что я сказал Family и Person, и имена не такие, но я испанский и тестирую некоторые операции с облачным типом, поэтому я еще не стандартизовал имена имен на русском языке;)

licensed under cc by-sa 3.0 with attribution.