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:
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.
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".
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
- 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); }]; }];
- 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]; }];
- 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!
[Bearbeiten] Ihre Frage mit relevantem Code, der Ihr Problem reproduziert. – rmaddy
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
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