To ask my question effectively, let's first consider the exact scenario I'm facing:
General Setup
Sequence of Events Leading to the Problem
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.
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.
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:
uniqueID
is equal to the parsed uniqueID.uniqueID
attribute to the parsed uniqueID.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?
On device A, tap the Add button (+) to show the photo picker, and then select a photo and add it to the Core Data store. Touch and hold the photo to display the context menu and then tap Create New Share to present the CloudKit sharing UI. Follow the UI to send a link to the Apple ID on device B.
Share Extension is an easy way that Apple provides to share contents (images, audio, files, etc.) from one app to another, even made by different developers.
App extensions let you extend custom functionality and content beyond your app and make it available to users while they're interacting with other apps or the system.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With