Reputation: 21
I've got a really frustrating problem that I'd like help understanding and perhaps even fixing.
I'm learning Core Data by building a rather simple app.
My model is as follows:
User
Attributes
Relationships
hasCompletedItem - "A user can complete many list items, A list item can be completed by many users"
isInAgeGroup - "A user is in one AgeGroup, an AgeGroup can contain multiple users"
AgeGroup
Attributes
Relationships
hasItems - "An AgeGroup has many ListItems associated with it, a ListItem can only be associated with one AgeGroup"
hasUsers - "An AgeGroup has many users associated with it, A user can only be in 1 AgeGroup"
ListItem
Attributes
Relationships
completedByUser - "A list item can be completed by many users, a user can complete many list items".
forAgeGroup - "A list item is assigned to a single AgeGroup, an AgeGroup can have multiple listItems"
My program is set up as follows:
id navigationController = [[self window] rootViewController];
id controller = [navigationController topViewController];
[controller setManagedObjectContext:[self managedObjectContext]];
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:@"selectprofile"]) {
[[segue destinationViewController] setManagedObjectContext:[self managedObjectContext]];
}
}
AddProfileViewController *viewController = (AddProfileViewController *)[[segue destinationViewController] topViewController];
[viewController setManagedObjectContext:self.managedObjectContext];
User *newMO = [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:_managedObjectContext];
newMO.name = self.nameTextField.text;
newMO.dob = _dob;
// etc
NSFetchRequest *ageGroupRecordRequest = [NSFetchRequest fetchRequestWithEntityName:@"AgeGroup"];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"title"
ascending:YES];
[ageGroupRecordRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
// Make a predicate to find the correct AgeGroup based on what was calculated
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(title == %@)", ageGroup];
[ageGroupRecordRequest setPredicate:predicate];
// Run the fetch request, should only ever get 1 result back.
NSError *fetchError;
// Result will be an array with a single AgeGroup entity
NSArray *fetchedObjects = [_managedObjectContext executeFetchRequest:ageGroupRecordRequest error:&fetchError];
// Set up the relationship using the fetched entity
// Crashes when saving if below line is uncommented
newMO.isInAgeGroup = fetchedObjects[0];
As noted in the code, when the line is uncommented and the managedObjectContext goes to save, i get the following error:
CoreData: error: failed to resolve optimistic locking failure: optimistic locking failure with (null) CoreData: error: failed to resolve optimistic locking failure. Old save request was: { inserts (( "0x942dd40 " )), updates (( "0x8e4ed60 " )), deletes () locks () } 2014-04-12 20:00:09.482 ChekList[6076:60b] CoreData: error: failed to resolve optimistic locking failure. Next attempt will be: { inserts (( "0x942dd40 " )), updates (( "0x8e4ed60 " )), deletes () locks () } sql: BEGIN EXCLUSIVE annotation: getting max pk for entityID = 3 annotation: updating max pk for entityID = 3 with old = 4017 and new = 4018 sql: COMMIT sql: BEGIN EXCLUSIVE sql: UPDATE ZAGEGROUP SET Z_OPT = ? WHERE Z_PK = ? AND (Z_OPT = ? OR Z_OPT IS NULL) details: SQLite bind[0] = (int64)1 details: SQLite bind[1] = (int64)6 details: SQLite bind[2] = nil sql: ROLLBACK sql: SELECT Z_PK,Z_OPT FROM ZAGEGROUP WHERE Z_PK IN (6) ORDER BY Z_PK annotation: sql execution time: 0.0006s
My understanding is that basically something else has modified the context, and during the save CoreData has noticed this and stopped. I've read about changing the MergePolicy on the ManagedObjectContext but I don't really want to do this without knowing why I have to.
It's interesting to note that if I comment out the line that attempts to set the relationship it works fine. (except of course the relationship isn't set)
As far as I can see I am passing the managedObjectContext correctly to each view controller. I have also made sure that there are not multiple contexts accessing the same persistent store.
Is it likely to be the FetchedResultsController in the previous View Controller that is modifying the context for some reason?
Is anyone able to offer some information and perhaps a possible solution? I'd rather not have to change the merge policy. I can't see why I should have to considering its a rather simple example. I've been pulling my hair out most of the day on this.
I can't help but think it's most likely something simple I'm missing.
Thanks, Brett.
Upvotes: 0
Views: 3668
Reputation: 838
I had this same issue but the suggested solution wasn't working for me. What worked was to create the context using concurrency type: NSMainQueueConcurrencyType
even though I don't need it and I'm not using concurrency at all but somehow copying the preloaded database created the optimistic locking failure. Then wrap the save call in a the context's performBlockAndWait:
To create the context:
[[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
To save changes:
__block NSError *error = nil;
__block BOOL savedOK = NO;
[managedObjectContext performBlockAndWait:^{
savedOK = [managedObjectContext save:&error];
}];
This is even documented in iOS API, look for Concurrency inside NSManagedObjectContext
class.
Upvotes: 0
Reputation: 21
I was able to figure out the solution.
Turns out that my whole problem was caused by my preloaded database. I have a preloaded database that I copy over in the AppDelegate. Turns out that something was wrong with it.
I found this out by commenting out the lines that copied the database over and instead manually added the preloaded data in the AppDelegate. I was able to add data and also set the relationships.
From there I created a new preloaded database and it works fine.
I also found a great Apple developer example -
https://developer.apple.com/library/ios/samplecode/iPhoneCoreDataRecipes/Introduction/Intro.html
Which also showed me an alternative way of adding new entities. In this case they create the actual entity in the prepareForSegue method and pass just the entity not the entire context.
I changed my app to use that method as well (before I figured out that the database was the issue) but it still crashed. I've decided to keep it that way though as it seems more elegant.
Upvotes: 2