如何保证主机应用程序和扩展程序使用的共享应用程序容器中的Core Data存储中的唯一条目?

时间:2014-11-19 15:09:19

标签: ios sqlite core-data watchkit ios-extensions

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

常规设置

  • 主机iOS 8应用。
  • 与主机应用程序捆绑在一起的一个或多个iOS 8扩展程序(WatchKit,Share等)。
  • 主机应用和所有扩展程序在共享应用程序组容器中共享相同的Core Data SQLite存储。
  • 每个app / extension都有自己的NSPersistentStoreCoordinator和NSManagedObjectContext。
  • 每个持久性存储协调器都使用持久存储,该存储在组容器中与所有其他持久性存储共享相同的SQLite资源。
  • 应用和所有扩展使用通用代码库来同步互联网上远程API资源的内容。

导致问题的事件顺序

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

  2. 同时,用户还会启动其中一个主机应用程序的扩展程序。它也可以从同一个远程API执行某种提取。它还会在解析API响应时尝试执行“upsert”。

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

  4. 核心数据Upsert序列:

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

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

      据我所知,Core Data没有任何方法可以将属性标记为唯一。我需要一个等同于SQLite INSERT OR IGNORE + UPDATE combo.的核心数据。或者我需要一种方法来“锁定”持久存储的SQLite后备存储,这听起来像是一个麻烦的配方。

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

2 个答案:

答案 0 :(得分:10)

对此最简单的方法似乎是首先避免使用多个编写器。为什么不完全从缓存数据驱动扩展,然后只从主iOS应用程序更新数据存储?

答案 1 :(得分:10)

  

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

是的,这与将iCloud与核心数据一起使用时采用的方法相同:让重复发生,然后再进行清理。这两种情况都存在创建重复条目的风险,并且没有完全可靠的方法来阻止它们。由于您拥有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和一个使用该值的次数。运行字典并适当处理重复项。

我在a blog post中更详细地描述了这一点。还有一个来自Apple的示例项目演示了这个名为SharedCoreData的过程,但我相信它只能作为WWDC 2012 sample code bundle的一部分使用。该会议的会议227也对此进行了描述。