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

jar*_*air 18 sqlite core-data ios watchkit ios-extensions

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

常规设置

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

导致问题的事件顺序

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

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

  3. 问题:如果主机应用程序和扩展程序同时尝试为同一API实体插入Core Data条目,会发生什么?要了解这是如何产生的,让我们看看upsert的事件序列:

核心数据Upsert序列:

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

问题详情

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

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

对iOS 8扩展引入的这个相当新颖的问题有一个已知的方法吗?

Dav*_*ong 10

看起来最简单的方法就是首先简单地避免使用多个编写器.为什么不完全从缓存数据驱动扩展,然后只从主iOS应用程序更新您的数据存储?


Tom*_*ton 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];
Run Code Online (Sandbox Code Playgroud)

这几乎就是SQL中类似这样的核心数据:

SELECT uniqueID, COUNT(uniqueID) FROM MyEntityName GROUP BY uniqueID;
Run Code Online (Sandbox Code Playgroud)

您将获得一个字典数组,每个字典都包含一个字典uniqueID和计数值的计数.运行字典并适当处理重复项.

我在博文中更详细地描述了这一点.还有一个来自Apple的示例项目,演示了名为SharedCoreData的过程,但我相信它只作为WWDC 2012示例代码包的一部分提供.该会议的会议227也对此进行了描述.