我如何保证Core Data存储中的主机应用程序和扩展程序使用的共享应用程序容器中的唯一条目? [英] How can I guarantee unique entries in a Core Data store in a shared app container used by both the host app and an extension?

查看:186
本文介绍了我如何保证Core Data存储中的主机应用程序和扩展程序使用的共享应用程序容器中的唯一条目?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



常规设置
$ b


  • 主机iOS 8应用程式。

  • 一个或多个iOS 8扩充功能(WatchKit,Share等)主机应用程序。

  • 主机应用程式和所有扩充功能在共用应用程式群组容器中共用同一个Core Data SQLite存储。

  • 每个应用程式/扩充功能都有自己的NSPersistentStoreCoordinator

  • 每个持久性商店协调器使用一个永久性商店,该商店在群组容器中与所有其他持久性商店共享相同的SQLite资源。

  • 应用程式和所有扩充功能都使用公用程式码来同步处理网际网路上远程API资源的内容。



导致问题


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


  2. 同时,用户还会启动其中一个主机应用程序的扩展程序。它也从相同的远程API执行某种类型的提取。


  3. 问题:如果主机应用程式和扩展尝试同时为同一个API实体升级一个Core Data条目?要了解如何实现这一点,让我们来看看upsert的事件顺序:


数据上传顺序:


  1. API解析代码解析给定API实体的uniqueID。

  2. 解析器针对与 uniqueID 等于解析的唯一ID的谓词匹配的任何条目执行Core Data fetch。

  3. 如果未找到现有条目,则解析器为此API实体插入一个新的Core Data条目,将其 uniqueID 属性插入到已解析的唯一ID。

  4. 解析器保存受管对象上下文,将新条目数据推送到SQLite后端存储。

strong>详细问题



让我们假设主机应用程序和扩展程序同时独立解析同一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];

这几乎是Core Data中等效的SQL:

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

您得到一个字典数组,每个字典包含一个 uniqueID 以及使用该值的次数的计数。运行字典并适当处理重复。



我在博客文章。还有一个来自Apple的示例项目,名为SharedCoreData,但我相信它只能作为WWDC 2012示例代码束。在会议第227届会议上也对此进行了描述。


To ask my question effectively, let's first consider the exact scenario I'm facing:

General Setup

  • A host iOS 8 app.
  • One or more iOS 8 extensions (WatchKit, Share, etc.) bundled with the host app.
  • The host app and all extensions share the same Core Data SQLite store in the shared app group container.
  • Each app/extension has its own NSPersistentStoreCoordinator and NSManagedObjectContext.
  • Each persistent store coordinator uses a persistent store that shares the same SQLite resources in the group container as all the other persistent stores.
  • The app and all extensions use a common codebase for syncing content from a remote API resource on the Internet.

Sequence of Events Leading to the Problem

  1. The user launches the host app. It begins fetching data from the remote API resource. Core data model objects are created based on the API response and "upserted" into the host app's managed object context. Each API entity has a uniqueID that identifies it in the remote API backend. By "upsert," I mean that for each API entity, the host app only creates a new entry in Core Data if an existing entry for a given uniqueID cannot be found.

  2. Meanwhile, the user also launches one of the host app's extensions. It, too, performs some kind of fetch from the same remote API. It also attempts to perform an "upsert" when parsing the API responses.

  3. The Problem: What happens if both the host app and an extension try to upsert a Core Data entry for the same API entity at the same time? To see how this could come about, let's look at the sequence of events for an upsert:

Core Data Upsert Sequence:

  1. The API parsing code parses the uniqueID for a given API entity.
  2. The parser performs a Core Data fetch for any entry that matches a predicate where uniqueID is equal to the parsed uniqueID.
  3. If an existing entry is not found, the parser inserts a new Core Data entry for this API entity, set's its uniqueID attribute to the parsed uniqueID.
  4. The parser saves the managed object context, which pushes the new entry data down to the SQLite backing store.

Problem in Detail

Let's assume the host app and the extension are independently parsing an API response for the same API entity at the same time. If both the host app and an extension reach Step 3 before either of them has finished Step 4, then they will both be trying to insert a new Core Data entry for the same uniqueID. When they reach Step 4 and call save: on their respective managed object contexts, Core Data will happily create duplicate entries.

As far as I'm aware, Core Data doesn't have any way to mark an attribute as unique. I need a Core Data equivalent to a SQLite INSERT OR IGNORE + UPDATE combo.. Or else I need a way to "lock" the persistent store's SQLite backing store, which sounds like a recipe for trouble.

Is there a known approach to this rather novel problem introduced by iOS 8 extensions?

解决方案

Is there a known approach to this rather novel problem introduced by iOS 8 extensions?

Yes, it's the same approach that applies when using iCloud with Core Data: let the duplicates happen, but then go and clean them up. Both situations run the risk of creating duplicate entries, and there's no completely reliable way to prevent them. Since you have a uniqueID key, you're in good shape as far as this is concerned.

It would be a lot easier, as Dave DeLong notes, to avoid the problem in the first place. If that's impossible, you can deal with it, with some extra work.

Finding duplicates would be something like:

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

This is pretty much the Core Data equivalent of something like this in SQL:

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

You get an array of dictionaries, each of which contains a uniqueID and a count of the number of times that value is used. Run through the dictionary and deal with duplicates appropriately.

I described this in more detail in a blog post. There's also a sample project from Apple that demonstrates the process, called SharedCoreData, but I believe it's only available as part of the WWDC 2012 sample code bundle. It was also described in session 227 at that conference.

这篇关于我如何保证Core Data存储中的主机应用程序和扩展程序使用的共享应用程序容器中的唯一条目?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆