iOS核心数据"乐观锁定失败"

时间:2014-04-12 10:12:05

标签: ios objective-c core-data

我有一个非常令人沮丧的问题,我希望帮助理解甚至修复。

我通过构建一个相当简单的应用程序来学习核心数据。

我的模型如下:

用户

属性

  • 名称
  • 性别
  • DOB

关系

  • hasCompletedItem - "用户可以完成许多列表项,列表项可以由许多用户完成"

  • isInAgeGroup - "用户在一个AgeGroup中,AgeGroup可以包含多个用户"

AgeGroup

属性

  • 标题

关系

  • hasItems - " AgeGroup有许多与之关联的ListItem,ListItem只能与一个AgeGroup关联"

  • hasUsers - " AgeGroup有很多与之关联的用户,一个用户只能在1个AgeGroup"

列表项

属性

  • itemText

关系

  • completedByUser - "列表项可以由许多用户完成,用户可以完成许多列表项"。

  • forAgeGroup - "列表项目分配给单个AgeGroup,AgeGroup可以有多个listItems"

我的程序设置如下:

  • AppDelegate处理创建CoreData堆栈。
  • didFinishLaunchingWithOptions访问MenuViewController并传递给managedObjectContext以供其使用:
 
id navigationController = [[self window] rootViewController];
id controller = [navigationController topViewController];
[controller setManagedObjectContext:[self managedObjectContext]];
  • 显示MenuViewController。它是一个带有几个按钮的UIViewController,每个按钮都与其他视图控制器相对应。
  • 当'开始'在MenuViewController上按下按钮,现有的managedObjectContext在prepareForSegue方法中传递:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"selectprofile"]) {
        [[segue destinationViewController] setManagedObjectContext:[self managedObjectContext]];
    }
}
  • 然后显示SelectProfileTableViewController。它列出了表中的每个用户。它有一个按钮,允许添加新用户。 tableview的数据由NSFetchedResultsController提供,它基本上只从User实体中获取所有记录。
  • 点击"添加"按钮将加载AddProfileViewController,但不会在传递managedObjectContext之前加载:
AddProfileViewController *viewController = (AddProfileViewController *)[[segue destinationViewController] topViewController];
[viewController setManagedObjectContext:self.managedObjectContext];
  • AddProfileViewController只是一个UIViewController。它有名称为D.O.B等的textFields。
  • 它有一个方法addProfileDone,当用户点击" Done"按钮。在此方法中,将创建一个新的User managedObject并设置其属性:
User *newMO = [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:_managedObjectContext];

newMO.name = self.nameTextField.text;
newMO.dob = _dob;
// etc
  • 接下来,它还尝试设置此新用户实体与其对应的AgeGroup实体之间的关系。
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];
  • 如代码中所述,当取消注释该行并且managedObjectContext进行保存时,我收到以下错误:

    CoreData:错误:无法解决乐观锁定失败:乐观锁定失败(null) CoreData:错误:无法解决乐观锁定失败。旧保存请求是:{inserts((     " 0x942dd40" )), 更新 ((     " 0x8e4ed60" )),deletes()locks()} 2014-04-12 20:00:09.482 ChekList [6076:60b] CoreData:错误:无法解决乐观锁定失败。下一次尝试将是:{inserts((     " 0x942dd40" )), 更新 ((     " 0x8e4ed60" )),deletes()locks()} sql:BEGIN EXCLUSIVE 注释:获取entityID = 3的max pk 注释:使用old = 4017和new = 4018更新entityID = 3的max pk sql:COMMIT sql:BEGIN EXCLUSIVE sql:UPDATE ZAGEGROUP SET Z_OPT =?在哪里Z_PK =? AND(Z_OPT =?或Z_OPT为空) 详细信息:SQLite bind [0] =(int64)1 详细信息:SQLite bind [1] =(int64)6 细节:SQLite bind [2] = nil sql:ROLLBACK sql:SELECT Z_PK,Z_OPT FROM ZAGEGROUP WHERE Z_PK IN(6)ORDER BY Z_PK 注释:sql执行时间:0.0006s

我的理解是,基本上其他东西已经修改了上下文,并且在保存期间CoreData注意到并停止了。我已经阅读过有关在ManagedObjectContext上更改MergePolicy的内容,但我并不想知道为什么必须这样做。

值得注意的是,如果我注释掉试图设置关系的行,它就能正常工作。 (当然除了关系没有设定)

据我所知,我正在将managedObjectContext正确传递给每个视图控制器。我还确保没有多个上下文访问同一个持久存储。

是否可能是由于某种原因正在修改上下文的上一个View Controller中的FetchedResultsController?

是否有人能够提供一些信息,也许还有可能​​的解决方案?我宁愿不必改变合并政策。我不明白为什么我应该考虑一个相当简单的例子。在这一天的大部分时间里,我一直在拔头发。

我无能为力,但我认为这很容易让我失踪。

谢谢, 布雷特。

2 个答案:

答案 0 :(得分:2)

我能够找到解决方案。

事实证明我的整个问题是由我预装的数据库引起的。 我有一个预加载的数据库,我在AppDelegate中复制。事实证明它出了问题。

我通过注释复制数据库的行来找到这一点,而是在AppDelegate中手动添加预加载的数据。我能够添加数据并设置关系。

从那里我创建了一个新的预加载数据库,它工作正常。

我还找到了一个很棒的Apple开发者示例 -

https://developer.apple.com/library/ios/samplecode/iPhoneCoreDataRecipes/Introduction/Intro.html

这也向我展示了添加新实体的另一种方式。在这种情况下,他们在prepareForSegue方法中创建实际实体,并仅传递实体而不是整个上下文。

我改变了我的应用程序以使用该方法(在我发现数据库是问题之前)但它仍然崩溃了。我决定保持这种方式,因为它似乎更优雅。

答案 1 :(得分:0)

我有同样的问题,但建议的解决方案对我不起作用。有用的是使用并发类型创建上下文:NSMainQueueConcurrencyType即使我不需要它,我根本不使用并发,但以某种方式复制预加载的数据库创建了乐观锁定失败。然后将保存调用包含在上下文的performBlockAndWait:

创建上下文:

[[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

保存更改:

__block NSError *error = nil;
__block BOOL savedOK = NO;
[managedObjectContext performBlockAndWait:^{
   savedOK = [managedObjectContext save:&error];
}];

甚至在iOS API中都有记录,在NSManagedObjectContext类中寻找Concurrency