2017-07-13 2 views
3

So habe ich ein wirklich seltsames Problem, ich nehme an, ich verstehe entweder nicht, wie CloudKit unter der Haube funktioniert oder ich habe einen Fehler in CloudKit gefunden.Das Löschen eines CKRecord ist wirklich verwirrend

So sieht die Ausgabe wie folgt aus:

App Anfangszustand:

I 5 "Package" Aufzeichnungen haben, sie nennen wir A, B, C, D, E.

Benutzeraktion

Der Benutzer wird „Package“ Rekord E löschen und zu einem späteren Zeitpunkt wird er eine Refresh-Taste drücken, die all aktuelle „Package“ Datensätze holen aus der Wolke.

Das Problem

Wenn der Benutzer den Refresh-Button drückt, sucht die App wird grundsätzlich auf den lokal gespeicherten „Package“ Aufzeichnungen existieren, und wird eine CKQuery mit einem Prädikat erstellen, die alle anderen Datensätze holen sollte, dass existieren nicht lokal. Der nächste Schritt besteht darin, die Methode [Datenbank ausführenAbfrage: inZoneWithID: completionHandler:] aufzurufen.

Die Überraschung zeigt sich, wenn ich die Ergebnisse erhalte, die den "Paket" -Eintrag E enthalten, den der Benutzer zuvor gelöscht hat.

Dies scheint nicht richtig zu mir zu sein ...

Die Schritte, die ich zu debuggen nahm:

  1. Gleich nach dem „Paket“ Rekord E löschen, habe ich eine CKFetchRecordsOperation und versuchte den gelöschten Datensatz zu holen. Das Ergebnis war wie erwartet: Ich habe einen "Datensatz nicht gefunden". Ich bin cool hier.

  2. In der Annahme, dass es auf der Serverseite einige Verzögerungen geben könnte, habe ich einen dispatch_after-Block gesetzt und den gleichen Abrufvorgang wie in Punkt 1, aber nach 30 Sekunden gestartet. Das Ergebnis war immer noch wie erwartet: Ich bekam den Fehler "Datensatz nicht gefunden".

  3. Ich habe den gleichen Test wie in Punkt 2 durchgeführt, aber mit einer Verzögerung von 100 Sekunden und ... Überraschung, die Operation CKFetchRecordsOperation gab das Paket des gelöschten Datensatzes E zurück. Das Seltsame ist, dass etwas immer noch einen Fehler zurückgibt, aber manchmal wird einfach das gelöschte Objekt zurückgeben.

Und nun das wirklich seltsam Teil: Dies geschieht nicht mit Rekord A, B, C und D, der einzigen Unterschied zwischen allen Thesen Aufzeichnungen ihre Namen sind. Das macht kein sinn.

ich einen Fehlerbericht gefüllt, und die Antwort, die ich diese bekam, war:

Das richtige Verhalten ist. Abfragen sind schließlich konsistent, sodass die Löschvorgänge möglicherweise nicht sofort bei der Abfrage berücksichtigt werden. Das Abrufen des gelöschten Datensatzes nach ID über eine CKFetchRecordsOperation sollte sofort ein CKErrorUnknownItem zurückgeben.

Während dies teilweise wahr ist, scheint dies nicht der Fall mit dem zu sein, was ich sehe.

-Code

  1. Löschen des Datensatzes E mit dem Namen DS2000330803AS, kehrt die Kontrolle CKFetchRecordsOperation Betrieb einen Fehler mit Datensatz nicht gefunden. Alles gut hier.
CKContainer *container = [CKContainer defaultContainer]; 
CKDatabase *privateDB = [container privateCloudDatabase]; 

CKRecordID *recordID = [[CKRecordID alloc] initWithRecordName: @"DS2000330803AS"]; 

CKModifyRecordsOperation *operation = [[CKModifyRecordsOperation alloc] initWithRecordsToSave: nil recordIDsToDelete: @[recordID]]; 
operation.database = privateDB; 

[operation setModifyRecordsCompletionBlock:^(NSArray<CKRecord *> * _Nullable savedRecords, 
             NSArray<CKRecordID *> * _Nullable deletedRecordIDs, 
             NSError * _Nullable error) { 

    CKFetchRecordsOperation *fetchOperation = [[CKFetchRecordsOperation alloc] initWithRecordIDs:@[recordID]]; 
    fetchOperation.database = privateDB; 
    [fetchOperation setPerRecordCompletionBlock:^(CKRecord * _Nullable record, CKRecordID * _Nullable recordID, NSError * _Nullable error){ 
     NSLog(@"Error: %@", error.localizedDescription); 
    }]; 
}]; 
  1. eine NSTimer in meinem VC Platzierung nur die Record Löschen zu testen, dieses Stück Code wird den gelöschten Datensatz zurück:
[NSTimer scheduledTimerWithTimeInterval:100 repeats:NO block:^(NSTimer * _Nonnull timer) { 

    CKContainer *container = [CKContainer defaultContainer]; 
    CKDatabase *privateDB = [container privateCloudDatabase]; 

    CKRecordID *recordID = [[CKRecordID alloc] initWithRecordName:@"DS2000330803AS"]; 

    CKFetchRecordsOperation *fetchOperation = [[CKFetchRecordsOperation alloc] initWithRecordIDs: @[recordID]]; 
    fetchOperation.database = privateDB; 
    [fetchOperation setPerRecordCompletionBlock:^(CKRecord * _Nullable record, CKRecordID * _Nullable recordID, NSError * _Nullable error){ 
     NSLog(@"Error: %@", error.localizedDescription); 
    }]; 

    [privateDB addOperation: fetchOperation]; 
}]; 
  1. Das Stück Code tha t ruft alle vorhandenen Datensätze ab, indem eine Aktualisierungsschaltfläche gedrückt wird, die der Benutzer jederzeit drücken kann. Ich habe diesen Code ein wenig vereinfacht, um das Problem bloßzustellen, im Grunde liefert die performQuery den Datensatz DS2000330803AS, und um meine geistige Gesundheit zu testen, füge ich eine CKFetchRecordsOperation hinzu, um den Datensatz erneut zu holen, was ihn natürlich ohne Probleme zurückgibt .
CKContainer *container = [CKContainer defaultContainer]; 
CKDatabase *privateDB = [container privateCloudDatabase]; 

NSPredicate *predicate = [NSPredicate predicateWithValue: YES]; 
CKQuery *query = [[CKQuery alloc] initWithRecordType:@"Package" predicate:predicate]; 

[privateDB performQuery:query 
    inZoneWithID:nil  completionHandler:^(NSArray<CKRecord *> * _Nullable results, NSError * _Nullable error) { 

    [results enumerateObjectsUsingBlock:^(CKRecord * _Nonnull record, NSUInteger idx, BOOL * _Nonnull stop) { 

     NSLog(@"Record ID: %@", record.recordID); 

     CKFetchRecordsOperation *fetchOperation = [[CKFetchRecordsOperation alloc] initWithRecordIDs: @[record.recordID]]; 
     fetchOperation.database = privateDB; 
     [fetchOperation setPerRecordCompletionBlock:^(CKRecord * _Nullable record, CKRecordID * _Nullable recordID, NSError * _Nullable error){ 
      NSLog(@"Error: %@", error.localizedDescription); 
     }]; 

     [privateDB addOperation: fetchOperation]; 
    }]; 
}]; 

Andere Anmerkungen: Ich entfernt und kommentiert so ziemlich alles im Zusammenhang mit Wolkenjunge und dem obigen Code ist der einzige, der mit Wolkenjunge in Wechselwirkung tritt. Ich teste gerade mit einem einzigen Gerät.

Ich weiß, dass die CKQuery ein besseres NSPredate haben kann, aber jetzt versuche ich zu verstehen, warum ich dieses Problem habe.

Ps.s. Als ich die erste Implementierung von CloudKit zu meiner App hinzugefügt habe, habe ich versucht, es so einfach wie möglich zu halten, ohne irgendwelche ausgefallenen Synchronisations-Sachen. Es funktionierte gut für ein Jahr, dann begann ich Berichte von meinen Benutzern zu erhalten, dass sie einige Datensätze in der Produktion nicht löschen können.

Irgendwelche Hinweise Jungs, wie ich das weiter debuggen sollte?

Vielen Dank!

+1

[Bearbeiten] Ihre Frage mit relevantem Code, der Ihr Problem reproduziert. – rmaddy

+2

Die Zeit, die für die Aktualisierung eines Datensatzes benötigt wird, ist nicht bekannt. Es gibt einige Indizierung, die zeitaufwendig sein kann. Hier ist eine Antwort, die ich gab, aber der Unterschied ist Hinzufügen von Datensätzen, obwohl die Sprache über Löschungen beachten. Wie ich damit umgehen würde ist ein Cache von gelöschten Datensätzen. Wenn sie nicht in den Ergebnissen sind, würde ich den Cache leeren. Im Grunde denken Sie daran, die Ergebnisse lokal zu verknüpfen, um sie so zu machen, wie sie der Benutzer erwartet. Link-https: //stackoverflow.com/questions/42729601/how-to-update-data-in-tableview-without-the-delay-using-cloudkit-when-creating-n/42731220#42731220 – agibson007

+1

iCloud ist nicht Orakel. Stellen Sie sich Dinge auf ihrer Seite vor; Da es eine Farm von iCloud-Servern geben muss, die alle miteinander synchronisiert sind, ist es ziemlich vernünftig, eine gewisse Latenz zu erwarten. Ich habe gewählt https://stackoverflow.com/users/3641744/agibson007 Antwort, weil es eine gute Lösung ist. Wenn Sie feststellen, dass CKFetchRecordsOperation auch das richtige Ergebnis zurückgibt, dann können Sie das alternativ auch, wenn Sie nicht lokal zwischenspeichern möchten. – user3069232

Antwort

1

Ich denke, Sie mischen Datensatz Typ und Record Name (String von CKRecordID). Der Name wird von CloudKit zugewiesen (normalerweise) und der Typ wird von Ihnen festgelegt. Ich würde wetten, dass es automatisch zugewiesen wurde, aber ich würde sehen müssen, wie der Rekord gespeichert wurde. Es würde bedeuten, einen Screenshot Ihres CloudKit Dashboards zu sehen.

In Ihrem Codeblock in 1) Sie versuchen, den Datensatznamen eines Datensatzes mit dem Datensatztyp zu löschen. Aus diesem Grund erhalten Sie den Fehler "Datensatz nicht gefunden" 2) Gleich wie Sie noch Record-Typ verwenden und nicht den Namen aufzeichnen 3) Ruft den Datensatz ab, weil es tatsächlich die zugewiesene record.recordID verwendet.

Das ist mein Bauch auf die Situation. Was das Löschen und Auffrischen betrifft, siehe meine answer Nähdatensätze, um Benutzeroberfläche und Datenbank synchron zu halten.

+1

Ich habe den Code überprüft und bin immer noch nicht sicher, ob ich den Namen und den Typ des Eintrags verwechsle, ich glaube tatsächlich, dass ich es nicht tue. Gemäß der Dokumentation von Apple kann man eine CKRecordID erstellen, indem man eine aussagekräftige Zeichenfolge bereitstellt: "Sie können Namen verwenden, die für Ihre App oder den Benutzer eine größere Bedeutung haben, solange jeder Name in der angegebenen Zone eindeutig ist." Das ist genau was ich tue, ich benutze den DS2000330803AS String als ID, weil ich weiß, dass diese Strings immer eindeutig sind. – Andy

+0

Mögen Sie Screenshot Record E aus dem Dashboard, nur um es zu sehen? Ich zweifle nicht an dir, aber ich ging von meiner besten Schätzung und verfügbaren Informationen ab. Wenn es in Dashboard erstellt wurde, ist es wahrscheinlich eine eindeutige Zeichenfolge, die von CloudKit zugewiesen wurde. – agibson007

+0

Hier ist es: https://www.dropbox.com/s/1xm3kv636w8m69l/Screenshot%202017-07-14%2017.25.57.png?dl=0 Das Seltsame ist, dass ich es geschafft habe, diesen Datensatz aus dem Dashboard zu löschen , und jetzt scheint es, dass es aus diesem "stecken gebliebenen" Zustand herausgekommen ist und es auch aus der App gelöscht werden kann (wenn ich es neu erstelle), denke ich, dass das jetzt irrelevant ist, weil es eine völlig neue Aufzeichnung ist. Das sind die Dinge, die mich bei der Implementierung eines Frameworks sehr erschrecken ... wir alle erinnern uns an die Kerndaten im Cloud-Fiasko ... – Andy

Verwandte Themen