2015-09-02 11 views
6

Właśnie zaczynam pracę z CloudKit, więc trzymaj się mnie.CloudKit - CKQueryOperation z zależnością

informacji Tło

Na WWDC 2015, Apple wygłosił o CloudKit https://developer.apple.com/videos/wwdc/2015/?id=715

W tym mówić, ostrzegają przed tworzeniem zapytań łańcuchowym i zamiast polecić tę taktykę:

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

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

Przykładowa struktura

Baza projekt testowy zawiera zwierzęta i ich właścicieli, wygląda to tak:

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

Moje pytanie

Próbuję znaleźć wszystkie zwierzęta należące do właściciela, a ja martwię "Tworzę jabłko łańcuchowe ostrzega przed. Zobacz poniżej dwie metody, które robią to samo, ale na dwa sposoby. Które z nich jest bardziej poprawne lub oboje są błędne? Czuję, że robię to samo, ale zamiast tego używam tylko bloków ukończenia.

Nie jestem pewien, jak zmienić otherSearchBtnClick: używać zależności. Gdzie muszę dodać

ownerQueryOp.addDependency(queryOp) 

w 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

Nie ma odpowiedzi, ponieważ jestem tak daleko od bazy Jestem beznadziejny ?! – Charlie

Odpowiedz

1

Teoretycznie można mieć wielu właścicieli, a zatem wiele zależności. Również zapytania wewnętrzne zostaną utworzone po tym, jak zapytanie zewnętrzne zostanie już wykonane. Będziesz za późno, aby utworzyć zależność. W twoim przypadku to chyba łatwiej wymusić wykonanie wewnętrznych zapytań do osobnej kolejce tak:

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

ten sposób będziesz mieć pewność, że każde zapytanie wewnętrzne zostaną wykonane na nowej kolejki i w tym czasie to zapytanie nadrzędne może zostać zakończone.

Coś jeszcze: aby uczynić twój kod czystszym, byłoby lepiej, gdyby cały kod wewnątrz pętli for był w oddzielnej funkcji z CKReference jako parametrem.

0

miałem ten sam problem niedawno i skończyło się używając NSBlockOperation przygotować drugą kwerendę i dodał zależność, aby to wszystko działa:

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) 

A teraz wszystko działa jak się spodziewałem, ponieważ można ustawić swoje operacje w bardzo szczegółowy sposób i wykonują w poprawnej kolejności ^.^

w Twoim przypadku chciałbym zorganizować to tak:

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) 

a teraz wszystko to należy zrobić jest dodanie ich do nowo utworzonej kolejki pracy po tym jak kierować je do naszej bazy danych

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

PS: wiem, że powiedział rodziny i osoby i nazwiska nie są tak, ale jestem hiszpanski i testowania niektórych operacji cloudkit, więc nie znormalizowane do angielskich nazw typów recor jeszcze;)

+0

Myślę, że można użyć queryCompletionBlock na getOwnerOperation, aby ustawić fetchPetsQuery na queryPetsForOurOwner. Podczas przemówienia WWDC 2015 powiedział, że te niestandardowe bloki ukończenia mają miejsce przed zakończeniem operacji, a on sam powiedział, że jest to czas na ustawienie danych dotyczących następnej operacji. Więc to zaoszczędziłoby ci stworzenie operacji blokowej. To wszystko teoria, której jeszcze nie próbowałem. – malhal

2

Jeśli nie potrzebujesz anulowania i nie martwi Cię ponowna próba popełnienia błędu sieciowego, to myślę, że dobrze pasujesz do zapytań.

Wiem, że wiem, w WWDC 2015 Nihar Sharma zalecał podejście polegające na uzależnianiu, ale wygląda na to, że po prostu rzucił to na samym końcu bez większego zastanowienia. Widzisz, nie można powtórzyć NSOperation, ponieważ i tak są one jednorazowe, a on nie zaproponował żadnego przykładu anulowania operacji już w kolejce lub przekazywania danych z jednej operacji z kolejnej. Biorąc pod uwagę te 3 komplikacje, które mogą wymagać tygodni do rozwiązania, po prostu trzymaj się tego, co pracujesz i czekaj na następne WWDC dla ich rozwiązania. Ponadto cały punkt bloków polega na umożliwieniu wywoływania metod wbudowanych i umożliwieniu dostępu do parametrów w powyższej metodzie, więc jeśli przejdziesz do operacji, nie uzyskasz pełnej korzyści z tej korzyści.

Jego głównym powodem, dla którego nie używa się łańcuchów, jest śmieszne, że nie mógł stwierdzić, który błąd jest związany z konkretną prośbą, miał nazwy swoich błędów, niektóre błędy, a następnie inne błędy itp. Nikt przy zdrowych zmysłach nie nazywa błędów w różnych blokach więc używaj tej samej nazwy dla wszystkich z nich, a potem wiesz, że wewnątrz bloku zawsze używasz właściwego błędu. W ten sposób był tym, który stworzył swój nieporządny scenariusz i zaproponował rozwiązanie, jednak najlepszym rozwiązaniem jest po prostu nie tworzenie nieprzyjemnego scenariusza wielu nazw parametrów błędów w pierwszej kolejności!

Ze wszystkim, co zostało powiedziane, w przypadku nadal chcesz spróbować użyć zależności eksploatacji tutaj jest przykładem tego, jak można to zrobić:

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

Jak to działa jest najpierw wyka rekord Venue, następnie pobiera swoją kategorię.

Niestety nie ma obsługi błędów, ale jak widać, było już mnóstwo kodu do zrobienia, co można zrobić w kilku liniach z łańcuchem. Osobiście uważam ten wynik za bardziej skomplikowany i dezorientujący niż po prostu połączenie metod wygody.

Powiązane problemy