Подтвердить что ты не робот

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

Чтобы эффективно задать вопрос, позвольте сначала рассмотреть точный сценарий, с которым я сталкиваюсь:

Общая настройка

  • Приложение хоста iOS 8.
  • Один или несколько расширений iOS 8 (WatchKit, Share и т.д.) в комплекте с хост-приложением.
  • Приложение-хост и все расширения совместно используют одно хранилище данных SQLite в общем контейнере группы приложений.
  • Каждое приложение/расширение имеет собственный NSPsistentStoreCoordinator и NSManagedObjectContext.
  • Каждый постоянный координатор хранилища использует постоянное хранилище, которое использует те же ресурсы SQLite в контейнере группы, что и все остальные постоянные хранилища.
  • Приложение и все расширения используют общую кодовую базу для синхронизации содержимого с удаленного ресурса API в Интернете.

Последовательность событий, ведущих к проблеме

  • Пользователь запускает хост-приложение. Он начинает получать данные из удаленного ресурса API. Объекты модели основных данных создаются на основе ответа API и "обновлены" в контексте объекта управляемого приложения. Каждый объект API имеет уникальный идентификатор, который идентифицирует его в удаленном интерфейсе API. Под "upsert" я подразумеваю, что для каждого объекта API приложение-хозяин создает новую запись в Core Data, если существующая запись для данного уникального идентификатора не может быть найдена.

  • Между тем пользователь также запускает одно из расширений хост-приложений. Он также выполняет какую-то выборку из того же удаленного API. Он также пытается выполнить "upsert" при анализе ответов API.

  • Проблема: Что произойдет, если и хост-приложение, и расширение попытаются одновременно обновить запись Core Data для одного и того же объекта API? Чтобы узнать, как это может произойти, давайте посмотрим на последовательность событий для upsert:

Последовательность обновления основных данных:

  • Код анализа API анализирует уникальный идентификатор для данного объекта API.
  • Анализатор выполняет выборку основных данных для любой записи, которая соответствует предикату, где uniqueID равна разобранному уникальному идентификатору.
  • Если существующая запись не найдена, анализатор вставляет новую запись Core Data для этого объекта API, устанавливает свой атрибут uniqueID в разобранный уникальный идентификатор.
  • Анализатор сохраняет контекст управляемого объекта, который подталкивает новые данные ввода в хранилище резервных копий SQLite.

Проблема в деталях

Предположим, что приложение-хозяин и расширение самостоятельно анализируют ответ API для одного и того же объекта API в одно и то же время. Если и хост-приложение, и расширение достигнут шага 3 до того, как любой из них завершит Шаг 4, они оба попытаются вставить новую запись Core Data для того же уникального идентификатора. Когда они достигнут шага 4 и вызовут save: в соответствующих контекстах управляемых объектов, Core Data с радостью создаст повторяющиеся записи.

Насколько мне известно, Core Data не имеет возможности пометить атрибут как уникальный. Мне нужен Core Data эквивалент SQLite INSERT OR IGNORE + UPDATE комбо.. Или мне нужен способ "заблокировать" хранилище резервных хранилищ SQLite, которое звучит как рецепт проблемы.

Есть ли известный подход к этой довольно новой проблеме, введенной расширениями iOS 8?

4b9b3361

Ответ 1

Есть ли известный подход к этой довольно новой проблеме, введенной расширениями iOS 8?

Да, это тот же подход, который применяется при использовании iCloud с Core Data: пусть дубликаты происходят, но затем идут и очищают их. В обеих ситуациях существует риск создания повторяющихся записей, и нет абсолютно надежного способа их предотвращения. Поскольку у вас есть ключ uniqueID, вы в хорошей форме, насколько это возможно.

Было бы намного проще, как отмечает Дэйв ДеЛонг, во избежание проблемы в первую очередь. Если это невозможно, вы можете справиться с этим, с некоторой дополнительной работой.

Поиск дубликатов будет примерно таким:

NSError *error = nil;
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
[moc setPersistentStoreCoordinator:self.persistentStoreCoordinator];

NSFetchRequest *fr = [[NSFetchRequest alloc] initWithEntityName:@"MyEntityName"];
[fr setIncludesPendingChanges:NO];

NSExpression *countExpr = [NSExpression expressionWithFormat:@"count:(uniqueID)"];
NSExpressionDescription *countExprDesc = [[NSExpressionDescription alloc] init];
[countExprDesc setName:@"count"];
[countExprDesc setExpression:countExpr];
[countExprDesc setExpressionResultType:NSInteger64AttributeType];

NSAttributeDescription *uniqueIDAttr = [[[[[_psc managedObjectModel] entitiesByName] objectForKey:@"MyEntityName"] propertiesByName] objectForKey:@"uniqueID"];
[fr setPropertiesToFetch:[NSArray arrayWithObjects:uniqueIDAttr, countExprDesc, nil]];
[fr setPropertiesToGroupBy:[NSArray arrayWithObject:uniqueIDAttr]];

[fr setResultType:NSDictionaryResultType];

NSArray *countDictionaries = [moc executeFetchRequest:fr error:&error];

Это почти эквивалент Core Data, аналогичный этому в SQL:

SELECT uniqueID, COUNT(uniqueID) FROM MyEntityName GROUP BY uniqueID;

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

Я описал это более подробно в сообщении в блоге. Также есть образец проекта от Apple, который демонстрирует процесс, называемый SharedCoreData, но я считаю, что он доступен только в составе образца кода WWDC 2012. Это также было описано на сессии 227 на этой конференции.

Ответ 2

Похоже, что самым простым подходом к этому было бы просто избежать множественных писателей в первую очередь. Почему бы не просто полностью отключить свои расширения от кэшированных данных, а затем обновить хранилище данных только из основного приложения iOS?