19

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

常规设置

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

导致问题的事件顺序

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

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

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

核心数据更新序列:

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

详细问题

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

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

是否有已知的方法来解决 iOS 8 扩展引入的这个相当新颖的问题?

4

2 回答 2

10

似乎最简单的方法是首先简单地避免多个写入器。为什么不直接从缓存数据中驱动你的扩展,然后只从你的主要 iOS 应用程序更新你的数据存储呢?

于 2014-11-19T17:08:29.243 回答
10

是否有已知的方法来解决 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 中类似的 Core Data 等价物:

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

你会得到一个字典数组,每个字典都包含一个uniqueID和一个使用该值的次数的计数。浏览字典并适当地处理重复项。

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

于 2014-11-19T17:59:19.977 回答