2015-09-02 3 views
6

Sto solo iniziando a lavorare con CloudKit, quindi abbi pazienza con me.CloudKit - CKQueryOperazione con dipendenza

informazioni Sfondo

al WWDC 2015, Apple ha dato un discorso su CloudKit https://developer.apple.com/videos/wwdc/2015/?id=715

In questo discorso, essi guardia contro la creazione di query concatenamento e raccomanda invece questa tattica:

let firstFetch = CKFetchRecordsOperation(...) 
let secondFetch = CKFetchRecordsOperation(...) 
... 
secondFetch.addDependency(firstFetch) 

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

Esempio di struttura

Il database di progetto di test contiene gli animali domestici e dei loro proprietari, sembra che questo:

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

La mia domanda

Sto cercando di trovare tutti gli animali che appartengono a un proprietario, e sono preoccupato che Sto creando la catena che mette in guardia contro Apple. Vedi sotto per due metodi che fanno la stessa cosa, ma in due modi. Quale è più corretto o sono entrambi sbagliati? Mi sento come se stessi facendo la stessa cosa, ma usando solo i blocchi di completamento.

Sono confuso su come modificare otherSearchBtnClick: per utilizzare la dipendenza. Dove avrei bisogno di aggiungere

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) 
} 
+0

Non ci sono risposte perché sono così lontano dalla base Sono senza speranza ?! – Charlie

risposta

1

in teoria si potrebbero avere più proprietari e quindi più dipendenze. Anche le query interne verranno create dopo che la query esterna è già stata eseguita. Sarai troppo tardi per creare una dipendenza. Nel tuo caso è probabilmente più facile per forzare l'esecuzione delle query interne a una coda separata come questo:

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

In questo modo si farà in modo che ogni query interna verrà eseguito su una nuova coda e nel frattempo quella query genitore può finire.

Qualcos'altro: per rendere il vostro codice più pulito, sarebbe meglio se tutto il codice all'interno del ciclo for fosse in una funzione separata con un CKReference come parametro.

0

Ho avuto lo stesso problema da poco e finito per usare un NSBlockOperation per preparare la seconda query e ha aggiunto una dipendenza per far funzionare il tutto:

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) 

e ora è lavorare come mi aspettavo, perché è possibile impostare le tue operazioni in un modo molto granulare e vengono eseguite nell'ordine corretto ^.^

nel tuo caso vorrei strutturare in questo modo:

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) 

ed ora tutto ciò che deve essere fatto è quello di aggiungerli alla coda di operazione appena creata dopo che li diretto al nostro database

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

PS: so che ho detto Famiglia e persona e i nomi non sono così, ma io sono spagnolo e testare alcune operazioni cloudkit, quindi non ho standardizzato per i nomi dei tipi Recor inglesi ancora;)

+0

Penso che sia possibile utilizzare queryCompletionBlock su getOwnerOperation per impostare fetchPetsQuery su queryPetsForOurOwner. Nel talk del WWDC 2015 ha detto che i blocchi di completamento personalizzati avvengono prima che l'operazione finisca e ha detto esplicitamente che è il momento di impostare i dati sulla prossima operazione. In questo modo si risparmia la creazione dell'operazione di blocco. Questa è tutta teoria che non ho ancora provato. – malhal

2

Se non ti serve la cancellazione e non ti preoccupi di riprovare con un errore di rete, allora penso che tu stia bene concatenando le query.

So che lo so, nel WWDC 2015 Nihar Sharma ha raccomandato l'approccio della dipendenza add, ma sembrerebbe che l'abbia buttato alla fine senza pensarci troppo. Si vede che non è possibile riprovare una NSOperation perché sono comunque one-shot, e non ha offerto alcun esempio per annullare le operazioni già in coda, o come passare i dati da una operazione alla successiva. Date queste 3 complicazioni che potrebbero richiedere settimane di risoluzione, limitatevi a ciò che state lavorando e aspettate il prossimo WWDC per la loro soluzione. Inoltre l'intero punto dei blocchi è quello di consentire di chiamare i metodi in linea e di essere in grado di accedere ai parametri nel metodo precedente, quindi se si passa alle operazioni non si ottiene il massimo vantaggio da tale beneficio.

Il motivo principale per cui non si utilizza il concatenamento è il ridicolo che non è in grado di dire quale errore è per la richiesta, ha chiamato i suoi errori Qualche errore poi altro Errore ecc. Nessuno nella loro ragione ha nomi errori param diversi all'interno dei blocchi quindi usa lo stesso nome per tutti e poi sai che all'interno di un blocco stai sempre usando l'errore giusto. Quindi è stato lui a creare il suo scenario disordinato ea offrirne una soluzione, tuttavia la soluzione migliore è semplicemente non creare in primo luogo lo scenario disordinato di più nomi di parametri di errore!

Con tutto ciò che viene detto, nel caso in cui si vuole ancora provare ad usare le dipendenze operative ecco un esempio di come potrebbe essere fatto:

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

Come funziona è un primo momento veccia un record Venue, quindi recupera la sua categoria.

Spiacente, non vi è alcuna gestione degli errori, ma come potete vedere era già una tonnellata di codice per fare qualcosa che potrebbe essere fatto in un paio di righe con concatenamento. E personalmente trovo questo risultato più complicato e confuso del semplice concatenare i metodi di convenienza.