Удаление почти дубликатов с помощью 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 ответа
Есть проблема, о которой упоминал Ларс. Вы должны быть осторожны, чтобы всегда делать вещи детерминистически. Сортировка по уникальному идентификатору - один из способов сделать это.
Лично я бы справился с этим одним из двух других способов:
- Делать дедупликацию после слияния (опять же, убедившись, что она детерминирована)
- Использование тщательно отобранных глобальных идентификаторов для контроля дедупликации для вас.
Например, вы можете использовать уникальный идентификатор Raphael
, Единственное, что вам нужно быть осторожным, это то, что когда вы создаете другого Рафаэля на той же машине, он называется Raphael_1
(или что угодно).
Если ваш уникальный идентификатор, скорее всего, будет уникальным (например, имя + фамилия вряд ли будут конфликтовать), ансамбли автоматически объединят человека на разных устройствах.
Что я считаю неправильным в вашем обработчике reparationContext, так это то, что вы удаляете локальный и сохраняете удаленный объект. Другое устройство сделает то же самое, но на этой стороне наоборот, и тогда удалит не тот объект. Метод репарации должен быть детерминированным. Поэтому, возможно, вы можете отсортировать двух людей по уникальному идентификатору или что-то еще и всегда удалять первое. Тогда все устройства будут делать то же самое, и не должно быть синхронизации с пинг-понгом, возвращающей удаленные данные.