Удаление CKRecord действительно сбивает с толку
Так что у меня действительно странная проблема, я полагаю, я либо не понимаю, как CloudKit работает под капотом, либо я столкнулся с ошибкой в CloudKit.
Итак, проблема выглядит так:
Начальное состояние приложения:
У меня есть 5 записей "Package", давайте назовем их A, B, C, D, E.
Действие пользователя
Пользователь удалит запись "Пакет" E и в какой-то более поздний момент времени нажмет кнопку обновления, которая выберет все текущие записи "Пакет" из облака.
Эта проблема
Когда пользователь нажимает кнопку обновления, приложение в основном просматривает существующие локально сохраненные записи "Package" и создает CKQuery с предикатом, который должен извлекать любые другие записи, которые не существуют локально. Следующим шагом в основном является вызов метода [database executeQuery: inZoneWithID:completeHandler:].
Сюрприз обнаруживается, когда я получаю результаты, которые содержат запись "Package" E, которую пользователь ранее удалил.
Это не кажется мне правильным...
Шаги, которые я предпринял для отладки:
Сразу после удаления записи "Package" E я создал CKFetchRecordsOperation и попытался извлечь удаленную запись. Результат оказался ожидаемым: я получил "Запись не найдена". Я здесь крутой.
Думая, что на стороне сервера могут быть некоторые задержки, я поставил блок dispatch_after и запустил ту же операцию извлечения, что и в пункте 1, но только через 30 секунд. Результат оказался таким же, как и ожидалось: я получил ошибку "Запись не найдена".
Выполнил тот же тест, что и в пункте 2, но с задержкой в 100 секунд и... неожиданно, операция CKFetchRecordsOperation вернула удаленный пакет E-записи. Странно то, что в некоторых случаях он по-прежнему возвращает ошибку, но иногда просто возвращает удаленный объект.
И теперь действительно странная часть: это не происходит с записями A, B, C и D, единственное различие между всеми записями тезисов - их имена. Это не имеет никакого смысла.
Я заполнил отчет об ошибке и получил следующий ответ:
Это правильное поведение. Запросы в конечном итоге становятся согласованными, поэтому удаление может не отражаться сразу при запросах. Извлечение удаленной записи по идентификатору с помощью CKFetchRecordsOperation должно немедленно вернуть CKErrorUnknownItem.
Хотя это отчасти верно, похоже, это не относится к тому, что я вижу.
Код
- При удалении записи E с именем DS2000330803AS операция check CKFetchRecordsOperation возвращает ошибку с записью не найдена. Все хорошо здесь.
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); }]; }];
- Поместив NSTimer в мой VC только для проверки удаления записи, этот фрагмент кода вернет удаленную запись:
[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]; }];
- Кусок кода, который выбирает все существующие записи, нажимая кнопку обновления, которую пользователь может нажать в любое время. Я немного упростил этот код, чтобы просто выявить проблему, по сути, executeQuery возвращает запись DS2000330803AS, и для проверки моей работоспособности я добавляю операцию CKFetchRecordsOperation, чтобы снова извлечь запись, которая, конечно же, возвращает ее без каких-либо проблем.,
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]; }]; }];
Другие примечания: я удалил и прокомментировал почти все, что связано с CloudKit, и приведенный выше код является единственным, который взаимодействует с CloudKit. Я сейчас тестирую на одном устройстве.
Я знаю, что CKQuery может иметь лучший NSPredicate, но теперь я пытаюсь понять, почему у меня есть эта проблема.
PS Когда я добавил в свое приложение первую реализацию CloudKit, я постарался сделать ее максимально простой, без каких-либо сложностей с синхронизацией. Это работало просто отлично в течение года, затем я начал получать отчеты от своих пользователей о том, что они не могут удалить некоторые записи в производственном процессе.
Любые намеки, ребята, как я должен дальше отлаживать это?
Спасибо!
1 ответ
Я думаю, что вы смешиваете тип записи и имя записи (строка CKRecordID). Имя назначается CloudKit(как правило), а тип задается вами. Могу поспорить, что это было назначено автоматически, но я должен был увидеть, как запись была сохранена. Было бы неплохо увидеть скриншот вашей панели инструментов CloudKit.
В вашем блоке кода в 1) вы пытаетесь удалить имя записи какой-либо записи, используя тип записи. Вот почему вы получаете сообщение об ошибке "Запись не найдена". 2) То же, что вы все еще используете тип записи, а не имя записи. 3) Получает запись, потому что она фактически использует назначенный record.recordID.
Это мое понимание ситуации. Что касается удаления и обновления, см. Мой agibson007 о сшивании записей для синхронизации пользовательского интерфейса и базы данных.