Here's a pattern I've used for creating unique "singleton" records.
Given a desired tag name, name
, and managed object context, context
,
- Using
context
, fetch all Tag objects with the desired name, ordered ascending by primary key. - If there is more than one result, return the first one. You're done.
- Otherwise, create a new, temporary, (peer) context,
tempContext
. - Using the temporary context,
- Create and insert a new Tag with the desired name,
newTag
, and save changes. - Fetch all Tag objects with the desired name, ordered ascending by primary key (just like step 1, but using
tempContext
). - The first result is the actual unique record,
tag
. - If the
tag
is not thenewTag
you created, that means someone else beat you to it. DeletenewTag
and save changes. - Get a local instance of the
tag
incontext
and return it.
The nice thing about this pattern is that it uses the underlying database as the synchronization mechanism, which allows multiple threads to create unique Tag objects without needing to coordinate with each other at the thread level.
Here's a rough, hand-written implementation of that pattern using Objective-C and Core Data, with some hand waving to skip over the nitty-gritty, and no error handling.
A caveat: I've only ever used this pattern with EOF, which is a close cousin of Core Data. It depends on being able to order fetched rows by an auto-incrementing primary key.
+ (Tag *)tagWithName:(NSString *)name inContext:(NSManagedObjectContext *)context
{
// `+fetchAllWithName:inContext:` fetches all instances of the entity
// by name in the context, ordered ascending by primary key.
NSArray *tags = [Tag fetchAllWithName: name inContext: context];
if ([tags count]) {
return [tags objectAtIndex:0];
}
// The tag doesn't already exist. The race is on to create the One True Instance.
Tag *tag;
NSManagedObjectContext *tempContext = [NSManagedObjectContext new];
[tempContext setPersistentStoreCoordinator: [context persistentStoreCoordinator]];
// Create a new instance:
Tag *newTag = [NSEntityDescription insertNewObjectForEntityForName:@"Tag" inManagedObjectContext:tempContext];
newTag.tagName = name;
[tempContext save:nil];
// Did we win the race?
tags = [Tag fetchAllWithName: name inContext: tempContext];
tag = [tags objectAtIndex:0];
if (tag != newTag]) {
// Oops, we lost the race, delete our tag:
[tempContext deleteObject: newTag];
[tempContext save:nil];
newTag = nil;
}
// Get a "local" instance in the original context:
return [context objectWithId: [tag objectID]];
}