Удаление почти дубликатов с помощью Core Data и Ensembles (iCloud)

Резюме

Моя проблема в том, что я хочу избавиться от почти дубликатов в моем проекте iOS на основе Core Data, который использует ансамбли для синхронизации с iCloud.

  • Синхронизация с iCloud работает в основном хорошо в моем приложении.
  • Проблема заключается в том, что пользователь создает похожие объекты на нескольких устройствах до того, как его постоянное хранилище будет передано Ensembles (подключенным к iCloud).
  • Это создает почти дубликаты, что на самом деле правильно.
  • Мой подход к удалению этих дубликатов, похоже, не работает.

Детальная проблема

Пользователь может создать NSManagedObjects на разных устройствах, прежде чем он подключен к iCloud. Допустим, у него есть NSManagedObject названный Car который имеет отношение "к одному" к NSManagedObject названный Person который в свою очередь имеет отношение ко многим Car, Это будет выглядеть так: Упрощенная модель

Хорошо, давайте представим, что у пользователя есть два устройства, и он создает два NSManagedObjects на каждом устройстве. Car названный "Ауди" и Person по имени "Рафаэль". Оба связаны через отношения. На другом устройстве он создает Car названный "BMW" и другой Person по имени "Рафаэль". Также связаны друг с другом. Теперь у пользователя есть два одинаковых объекта на каждом устройстве: Два Person объекты оба названы "Рафаэль".

Моя проблема в том, что у пользователя будет два Person объекты с именем "Рафаэль" на каждом устройстве после его синхронизации.

Это на самом деле правильно, так как объекты получают свои uniqueIdentifiers (для идентификации объектов в ансамблях), когда пользователь перехватывает свое постоянное хранилище. Объекты на самом деле разные. Но это то, что я хочу исправить.

Мой подход

Я реализовал этот метод делегата и удалил дубликаты в reparationContext.

- (BOOL)persistentStoreEnsemble:(CDEPersistentStoreEnsemble *)ensemble 
    shouldSaveMergedChangesInManagedObjectContext:(NSManagedObjectContext*)savingContext
    reparationManagedObjectContext(NSManagedObjectContext *)reparationContext {

    [reparationContext performBlockAndWait:^{

        // Find duplicates
        // Change relationships and only use the inserted Person object (the one from iCloud)
        // Delete local Person object
        [reparationContext save:nil];
    }
    return YES;
}

В основном это, кажется, хорошо работает на втором устройстве, которое объединяет данные с первого устройства. Но, к сожалению, кажется, что локальный человек все еще синхронизируется с iCloud, даже если он был удален в reparationContext.

Это приводит к нарушенному состоянию, поскольку первое устройство затем также объединяет изменения со второго устройства и заменяет человека, который уже был удален на втором устройстве. Некоторые синхронизации позже, когда человек окончательно отсутствует в отношениях с машиной, и приложение выдает ошибки синхронизации.

Шаги, чтобы воспроизвести проблему

  • Шаг 1 (Устройство 1)

    • Создавать объекты
    • Данные: Автомобиль "Ауди" -> Персона "Рафаэль (Устройство 1)"
  • Шаг 2 (Устройство 2)

    • Создавать объекты
    • Данные: Автомобиль "BMW" -> Персона "Рафаэль (Устройство 2)"
  • Шаг 3 (Устройство 1)

    • Пиявка данных из магазина
    • Подключиться к iCloud
    • Отправить данные в iCloud
    • Данные: Автомобиль "Ауди" -> Персона "Рафаэль (Устройство 1)"
  • Шаг 4 (Устройство 2)

    • Пиявка данных из магазина
    • Подключиться к iCloud
    • Слияние данных из iCloud
    • Заменить местного человека из устройства 2 на вставленного человека из устройства 1
    • Удалить местного человека из устройства 2
    • Отправить данные в iCloud
    • Данные:
      Автомобиль "Ауди" -> Персона "Рафаэль (Устройство 1)"
      Автомобиль "BMW" -> Персона "Рафаэль (Устройство 1)"
  • Шаг 5 (Устройство 1)

    • Слияние данных из iCloud
    • Замените местного человека из устройства 1 на вставленного человека из устройства 2 (этого не должно происходить)
    • Удалить местного человека из устройства 1 (этого не должно происходить)
    • Отправить данные в iCloud
    • Ожидаемые данные:
      Автомобиль "Ауди" -> Персона "Рафаэль (Устройство 1)"
      Автомобиль "BMW" -> Персона "Рафаэль (Устройство 1)"
    • Актуальные данные:
      Автомобиль "Ауди" -> Персона "Рафаэль (Устройство 2)"
      Автомобиль "BMW" -> Персона "Рафаэль (Устройство 2)"

На самом деле локальный объект "Рафаэль (устройство 2)" был удален на шаге 4, но кажется, что он все еще был отправлен в iCloud, потому что на шаге 5 он появляется как вставка в savingContext.insertedObjects от shouldSaveMergedChangesInManagedObjectContext метод делегата.

Насколько я понял, Ensembles сначала извлекает изменения из iCloud, спрашивает пользователя, все ли как положено с помощью методов делегата, затем сливается в постоянное хранилище и отправляет дельты в iCloud после объединения.

Я делаю что-то неправильно? Или это ошибка ансамбля?

2 ответа

Решение

Есть проблема, о которой упоминал Ларс. Вы должны быть осторожны, чтобы всегда делать вещи детерминистически. Сортировка по уникальному идентификатору - один из способов сделать это.

Лично я бы справился с этим одним из двух других способов:

  1. Делать дедупликацию после слияния (опять же, убедившись, что она детерминирована)
  2. Использование тщательно отобранных глобальных идентификаторов для контроля дедупликации для вас.

Например, вы можете использовать уникальный идентификатор Raphael, Единственное, что вам нужно быть осторожным, это то, что когда вы создаете другого Рафаэля на той же машине, он называется Raphael_1 (или что угодно).

Если ваш уникальный идентификатор, скорее всего, будет уникальным (например, имя + фамилия вряд ли будут конфликтовать), ансамбли автоматически объединят человека на разных устройствах.

Что я считаю неправильным в вашем обработчике reparationContext, так это то, что вы удаляете локальный и сохраняете удаленный объект. Другое устройство сделает то же самое, но на этой стороне наоборот, и тогда удалит не тот объект. Метод репарации должен быть детерминированным. Поэтому, возможно, вы можете отсортировать двух людей по уникальному идентификатору или что-то еще и всегда удалять первое. Тогда все устройства будут делать то же самое, и не должно быть синхронизации с пинг-понгом, возвращающей удаленные данные.

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