Удаление CKRecord действительно сбивает с толку

Так что у меня действительно странная проблема, я полагаю, я либо не понимаю, как CloudKit работает под капотом, либо я столкнулся с ошибкой в ​​CloudKit.

Итак, проблема выглядит так:

Начальное состояние приложения:

У меня есть 5 записей "Package", давайте назовем их A, B, C, D, E.

Действие пользователя

Пользователь удалит запись "Пакет" E и в какой-то более поздний момент времени нажмет кнопку обновления, которая выберет все текущие записи "Пакет" из облака.

Эта проблема

Когда пользователь нажимает кнопку обновления, приложение в основном просматривает существующие локально сохраненные записи "Package" и создает CKQuery с предикатом, который должен извлекать любые другие записи, которые не существуют локально. Следующим шагом в основном является вызов метода [database executeQuery: inZoneWithID:completeHandler:].

Сюрприз обнаруживается, когда я получаю результаты, которые содержат запись "Package" E, которую пользователь ранее удалил.

Это не кажется мне правильным...

Шаги, которые я предпринял для отладки:

  1. Сразу после удаления записи "Package" E я создал CKFetchRecordsOperation и попытался извлечь удаленную запись. Результат оказался ожидаемым: я получил "Запись не найдена". Я здесь крутой.

  2. Думая, что на стороне сервера могут быть некоторые задержки, я поставил блок dispatch_after и запустил ту же операцию извлечения, что и в пункте 1, но только через 30 секунд. Результат оказался таким же, как и ожидалось: я получил ошибку "Запись не найдена".

  3. Выполнил тот же тест, что и в пункте 2, но с задержкой в ​​100 секунд и... неожиданно, операция CKFetchRecordsOperation вернула удаленный пакет E-записи. Странно то, что в некоторых случаях он по-прежнему возвращает ошибку, но иногда просто возвращает удаленный объект.

И теперь действительно странная часть: это не происходит с записями A, B, C и D, единственное различие между всеми записями тезисов - их имена. Это не имеет никакого смысла.

Я заполнил отчет об ошибке и получил следующий ответ:

Это правильное поведение. Запросы в конечном итоге становятся согласованными, поэтому удаление может не отражаться сразу при запросах. Извлечение удаленной записи по идентификатору с помощью CKFetchRecordsOperation должно немедленно вернуть CKErrorUnknownItem.

Хотя это отчасти верно, похоже, это не относится к тому, что я вижу.

Код

  1. При удалении записи 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);
    }];
}];
  1. Поместив 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];
}];
  1. Кусок кода, который выбирает все существующие записи, нажимая кнопку обновления, которую пользователь может нажать в любое время. Я немного упростил этот код, чтобы просто выявить проблему, по сути, 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 о сшивании записей для синхронизации пользовательского интерфейса и базы данных.

Другие вопросы по тегам