如何保证主机应用和扩展使用的共享应用容器中的核心数据存储中的唯一条目



为了有效地提出我的问题,让我们首先考虑我面临的确切情况:

常规设置

  • 主机 iOS 8 应用程序。
  • 与主机应用捆绑的一个或多个 iOS 8 扩展(监视工具包、共享等)。
  • 主机应用和所有扩展在共享应用组容器中共享相同的核心数据 SQLite 存储。
  • 每个应用程序/扩展都有自己的NSPersistentStoreCoordinator和NSManagedObjectContext。
  • 每个持久存储
  • 协调器都使用一个持久存储,该存储与所有其他持久存储共享组容器中相同的 SQLite 资源。
  • 应用和所有扩展都使用通用代码库来同步来自互联网上远程 API 资源的内容。

导致问题的事件的顺序

  1. 用户启动主机应用。它开始从远程 API 资源获取数据。核心数据模型对象基于 API 响应创建,并"更新插入"到主机应用的托管对象上下文中。每个 API 实体都有一个唯一 ID,用于在远程 API 后端中标识它。通过"upsert",我的意思是,对于每个 API 实体,如果找不到给定唯一 ID 的现有条目,主机应用程序仅在核心数据中创建新条目。

  2. 同时,用户还会启动主机应用程序的扩展程序之一。它也从同一个远程 API 执行某种提取。它还尝试在解析 API 响应时执行"更新插入"。

  3. 问题:如果主机应用和扩展同时尝试更新同一 API 实体的核心数据条目,会发生什么情况?要了解这是如何实现的,让我们看一下 upsert 的事件顺序:

核心数据更新插入序列:

    API 解析
  1. 代码解析给定 API 实体的唯一 ID。
  2. 解析器对与谓词匹配的任何条目执行核心数据提取,其中uniqueID等于解析的唯一 ID。
  3. 如果未找到现有条目,解析器将为此 API 实体插入一个新的核心数据条目,将其uniqueID属性设置为解析的 uniqueID。
  4. 分析器保存托管对象上下文,这会将新条目数据向下推送到 SQLite 后备存储。

问题详解

假设主机应用和扩展同时独立分析同一 API 实体的 API 响应。如果主机应用和扩展在完成步骤 4 之前都到达步骤 3,则它们都将尝试为同一唯一 ID 插入新的 Core Data 条目。当他们到达步骤 4 并在各自的托管对象上下文上调用 save: 时,Core Data 将很乐意创建重复的条目。

我所知,Core Data没有任何方法将属性标记为唯一。我需要一个相当于SQLite INSERT OR IGNORE + UPDATE组合的核心数据。否则,我需要一种方法来"锁定"持久存储的SQLite后备存储,这听起来像是麻烦的秘诀。

对于iOS 8扩展引入的这个相当新颖的问题,是否有已知的方法?

对于iOS 8扩展引入的这个相当新颖的问题,是否有已知的方法?

是的,这与将iCloud与Core Data一起使用时适用的方法相同:让重复发生,然后去清理它们。这两种情况都有创建重复条目的风险,并且没有完全可靠的方法来防止它们。由于您有uniqueID钥匙,因此就这一点而言,您的状态良好。

正如戴夫·德龙(Dave DeLong)所指出的那样,首先避免这个问题要容易得多。如果这是不可能的,你可以处理它,做一些额外的工作。

查找重复项如下所示:

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];

这几乎相当于SQL中类似的核心数据:

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

您将获得一个字典数组,每个字典都包含一个uniqueID和该值的使用次数。运行字典并适当地处理重复项。

我在一篇博客文章中更详细地描述了这一点。还有一个来自Apple的示例项目演示了该过程,称为SharedCoreData,但我相信它仅作为WWDC 2012示例代码包的一部分提供。在那次会议上的第227届会议也对此作了描述。

似乎最简单的方法是首先简单地避免多个编写器。为什么不直接将扩展完全从缓存数据中移除,然后仅从主 iOS 应用更新数据存储?

最新更新