Глобальные идентификаторы? - iCloud + Core Data + Ensembles - дублирует при удалении объектов

Я пытаюсь реализовать синхронизацию iCloud в своем приложении Core Data. Я не профессионал в программировании, и это действительно сложная тема, которую я узнал... Я обнаружил, что Core Data Sync Framework "Ensembles" от Drew McCormack. Похоже, что сделать iCloud Sync намного проще.

Я интегрировал его в свое приложение, и синхронизация работает довольно хорошо, пока я добавляю новые объекты в свою модель базовых данных. Но когда я удаляю объект, он создает дубликаты. А потом дубликаты из дубликатов. В итоге у меня был один и тот же объект (объект), как 3-4 раза...

Это почему? Что я делаю неправильно? Я провел некоторое исследование и думаю, что глобальные идентификаторы могут решить эту проблему?

Что такое глобальные идентификаторы? Я думаю, что они помогают избежать дубликатов!? Но как мне установить это? Я действительно понятия не имею, провел много исследований, но не смог найти ответ на этот вопрос.

Спасибо за помощь!

Обновление: спасибо за помощь! Я читаю readme и книгу, но так как я новичок, мне не все ясно.

Я думаю, что теперь понимаю использование глобальных идентификаторов в ансамблях, но я не знаю, правильно ли я это делаю.

Если я правильно понимаю, я должен назначить идентификатор для каждого объекта. Я могу сделать это, сохранив его в атрибуте. Этот идентификатор может быть любым, если он уникален и является строкой NSString?

В моем приложении пользователь может хранить разные вещи, скажем, имя, текст, заголовок, дату и так далее. Приложение основано на шаблоне Master-Detail-View в Xcode и использует Core Data. Моя базовая модель данных имеет только одну сущность с некоторыми атрибутами, большинство из которых являются строками и NSDate. Нет отношений или что-нибудь. Если пользователь нажимает "+", создается новый объект, и я сохраняю то, что пользователь вводит в атрибуты.

Что я сделал, чтобы добавить глобальные идентификаторы, так это добавить новый атрибут, в котором он хранится. Поэтому, когда создается новый объект, я делаю

/// I did find that to use as identifier !?

NSString *taskUniqueStringKey = newManagedObject.objectID.URIRepresentation.absoluteString;

/// and store it in the attribute.

[newManagedObject setValue:taskUniqueStringKey forKey:@"coreDataObjectID"]; 

Тогда я использую это:

- (NSArray *)persistentStoreEnsemble:(CDEPersistentStoreEnsemble *)ensemble globalIdentifiersForManagedObjects:(NSArray *)objects
{

return [objects valueForKeyPath:@"coreDataObjectID"];;

}

Кажется, это работает для меня. Но я делаю это правильно? Это правильное место для назначения глобального идентификатора? У меня нет пробуждения от Insert!?

Если это работает, я получил следующую проблему. Мое приложение уже работает, и в старых записях, которые пользователь сохранил до обновления, будет отсутствовать глобальный идентификатор. Что я могу с этим поделать? Я подумал, что я уже получил и что уникально, и единственное, о чем я могу думать, это атрибут, который сохраняет [дату NSDate] при создании объекта.

Я пытался использовать это, но мне не удалось, потому что ансамбли будут принимать только NSString, а не NSDate!? Могу ли я использовать этот атрибут даты, достаточно ли он уникален и работает ли как глобальный идентификатор? И если да, не могли бы вы дать мне пример кода, как преобразовать это из даты в строку?

Синхронизация с ансамблями работает довольно хорошо. Больше нет дубликатов, вы можете просто отключить iCloud, записи останутся и снова включить его, и он синхронизируется, как и должно, без потери локально сохраненных объектов или около того. Ансамбли это действительно круто! Я наблюдаю некоторые незначительные странные поведения, такие как иногда синхронизация занимает много времени, иногда она действительно быстрая, и если я редактирую вещи за короткий промежуток времени на двух разных устройствах, она становится немного испорченной, как объект, который я только что удалил, появляется снова. Но я думаю, это нормально? Если я возьму некоторое время между использованием приложения на разных устройствах, все будет работать нормально.

Правильно ли я понимаю, есть только один метод для синхронизации:

- (void)syncWithCompletion:(void(^)(void))completion
{
if (self.ensemble.isMerging) return;


if (!self.ensemble.isLeeched) {
    [self.ensemble leechPersistentStoreWithCompletion:^(NSError *error) {
        if (error) NSLog(@"Error in leech: %@", error);

        if (completion) completion();
    }];
}
else {
    [self.ensemble mergeWithCompletion:^(NSError *error) {

        if (completion) completion();
    }];
}

и вы просто позвоните, если нужно? Нет ничего лучше, чем делать слияние без пиявки раньше, или метод типа "это реальный статус - сохранить его, как сейчас"?

В приложении есть разные точки, где вы хотите синхронизировать. При запуске приложения и при завершении будет хорошим моментом. В моем приложении есть две точки, которые я должен синхронизировать, я думаю: при добавлении объекта и сохранении его в Core Data и при сохранении изменений в объекте. Я также мог бы предоставить кнопку типа "синхронизировать сейчас". Это хороший подход, и я всегда просто звоню

[self syncWithCompletion:NULL];

Еще один вопрос, который возник. Можно ли исключить объекты из синхронизации с ансамблями? Мое приложение загружает учебные записи как объекты один раз при первом запуске приложения. Я не хочу синхронизировать их, если это возможно как-то?

Большое спасибо за вашу помощь! Если бы я мог помочь вам с чем-то вроде локализации на немецком языке или так, дайте мне знать!;)

2 ответа

Да, это почти наверняка связано с тем, что вы не настроили глобальные идентификаторы для ваших объектов или, по крайней мере, не сделали это должным образом.

Когда вы передаете свой ансамбль, локальное постоянное хранилище импортируется в данные синхронизации. Без глобальных идентификаторов Ensembles будет назначать случайные идентификаторы вашим объектам, чтобы они могли отслеживать их на разных устройствах.

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

Решение - глобальные идентификаторы. Реализуя CDEPersistentStoreEnsemble Метод делегата, вы можете предоставить Ensembles глобальные идентификаторы, которые он может использовать для определения, какие объекты на разных устройствах принадлежат друг другу.

Что вы должны использовать для глобальных идентификаторов? Часто это просто UUID, хотя для одноэлементных объектов вы просто захотите выбрать идентификатор.

Вы можете инициализировать их в awakeFromInsert, Вы можете хранить глобальные идентификаторы в атрибутах своих объектов. (Обратите внимание, что если вы переносите существующее приложение, вам следует проверить с помощью выборки, были ли сгенерированы глобальные идентификаторы, ПРЕЖДЕ ЧЕМ вы пытаетесь отправить хранилище на синхронизацию.)

Больше подробностей в README на GitHub и в книге на leanpub.

Обновить

Чтобы ответить на ваши вопросы обновления:

Да, идентификатор просто должен быть строкой и быть неизменным. Он не должен меняться после назначения.

NSManagedObjectID не очень хороший глобальный идентификатор, так как он будет отличаться на разных устройствах. Мы действительно хотим что-то глобальное для всех устройств.

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

Если у вас есть существующее приложение, и оно синхронизировалось с помощью другого механизма, вам нужно найти способ предоставления одинаковых глобальных идентификаторов на каждом устройстве. Один из способов сделать это - изменить свойства объекта каким-либо образом. Обычно это дает вам значение, близкое к уникальному, и этого будет достаточно для перехода.

Например, вы делаете быструю выборку и обнаруживаете, что ваши объекты еще не имеют глобальных идентификаторов. Вы проходите через объекты и устанавливаете глобальные идентификаторы в строку, состоящую из creationDate + text. (Вы могли бы даже сократить это, взяв хеш, но это, вероятно, не так важно.) После этой первоначальной "миграции" на глобальные идентификаторы вы просто использовали бы UUID для любых вновь создаваемых объектов.

Обратите внимание, что вам не нужно использовать awakeFromInsert, Это просто удобное место для этого. Пока вы присваиваете глобальный идентификатор перед сохранением объекта, все будет в порядке.

Самый простой способ получить строку из NSDate это позвонить description метод, но другой способ будет получить double с помощью timeIntervalSince1970и превращая это в строку. (Будьте осторожны с самими датами как уникальными идентификаторами: часто объекты, созданные вместе, будут иметь одинаковую дату создания.)

Вы правы относительно того, как вы должны сделать синхронизацию: вы можете просто позвонить syncWithCompletion:,

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

  1. Поместите их в отдельное постоянное хранилище и добавьте это хранилище в тот же координатор постоянного хранилища.
  2. Синхронизируйте объекты, но дайте им глобальные идентификаторы вручную, чтобы объекты обрабатывались одинаково на каждом устройстве. Например. Вы можете просто указать глобальные идентификаторы: "Sample1", "Sample2" и т. Д.

Чтобы интегрировать ответ Дрю, я предполагаю, что два шага следующие.

1 Реализация CDEPersistentStoreEnsemble метод делегата (см. README)

- (NSArray *)persistentStoreEnsemble:(CDEPersistentStoreEnsemble *)ensemble 
    globalIdentifiersForManagedObjects:(NSArray *)objects {
    return [objects valueForKeyPath:@"yourUniqueIdentifier"];
}

2 Создайте уникальный идентификатор для NSManagedObject подкласс

- (void)awakeFromInsert {
    [super awakeFromInsert];

    if (!self.yourUniqueIdentifier) {
        self.yourUniqueIdentifier = [[NSUUID UUID] UUIDString];
    }
}

В awakeFromInsert Вы можете инициализировать специальные значения свойств по умолчанию, например, идентификатор.

Проверка необходима, например, когда у вас есть родительско-дочерние контексты. В противном случае вы перезаписываете ранее установленный идентификатор. Посмотрите, почему awakeFromInsert вызывается дважды?,

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