【问题标题】:iOS Core Data "optimistic locking failure"iOS Core Data“乐观锁定失败”
【发布时间】:2014-04-12 10:12:05
【问题描述】:

我有一个非常令人沮丧的问题,我想帮助理解甚至解决。

我正在通过构建一个相当简单的应用程序来学习 Core Data。

我的模型如下:

用户

属性

  • 姓名
  • 性别
  • dob

关系

  • hasCompletedItem - “一个用户可以完成很多列表项,一个列表项可以被很多用户完成”

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

年龄组

属性

  • 标题

关系

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

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

列表项

属性

  • 项目文本

关系

  • 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。它具有名称、DOB 等文本字段。
  • 它有一个 addProfileDone 方法,当用户点击“完成”按钮时会调用该方法。在这个方法中,创建了一个新的 User managedObject 并设置了它的属性:
User *newMO = [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:_managedObjectContext];

newMO.name = self.nameTextField.text;
newMO.dob = _dob;
// etc
  • 接下来,它还会尝试设置这个新的 User 实体与其对应的 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” )), 删除 () 锁 () } 2014-04-12 20:00:09.482 ChekList[6076:60b] CoreData:错误:无法解决乐观锁定失败。下一次尝试将是:{插入(( “0x942dd40” )), 更新 (( “0x8e4ed60” )), 删除 () 锁 () } sql:开始独家 注释:获取 entityID = 3 的最大 pk 注释:使用 old = 4017 和 new = 4018 更新 entityID = 3 的最大 pk sql: 提交 sql:开始独家 sql: 更新 ZAGEGROUP SET Z_OPT = ?在哪里 Z_PK = ? AND (Z_OPT = ? OR Z_OPT 为 NULL) 详细信息:SQLite 绑定 [0] = (int64)1 详细信息:SQLite 绑定 [1] = (int64)6 详细信息:SQLite 绑定 [2] = nil sql:回滚 sql: 从 ZAGEGROUP 中选择 Z_PK,Z_OPT,其中 Z_PK 在 (6) 按 Z_PK 排序 注解:sql执行时间:0.0006s

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

有趣的是,如果我注释掉试图设置关系的行,它可以正常工作。 (当然除了没有设置关系)

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

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

是否有人能够提供一些信息以及可能的解决方案?我宁愿不必更改合并策略。我不明白为什么我必须考虑它是一个相当简单的例子。这一天我大部分时间都在拔头发。

我不禁认为这很可能是我缺少的一些简单的东西。

谢谢, 布雷特。

【问题讨论】:

    标签: ios objective-c core-data


    【解决方案1】:

    我想出了解决办法。

    原来我的整个问题都是由我的预加载数据库引起的。 我有一个预加载的数据库,我在 AppDelegate 中复制它。原来它出了点问题。

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

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

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

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

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

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

    【讨论】:

      【解决方案2】:

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

      创建上下文:

      [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
      

      保存更改:

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

      这甚至记录在 iOS API 中,在 NSManagedObjectContext 类中查找 Concurrency

      【讨论】:

      • 发现这不是解决方案。问题是在将对象添加到该表的其他非自反关系之前,需要保存我的自反关系。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-17
      • 2018-04-27
      • 1970-01-01
      • 2015-03-25
      • 2011-05-21
      • 2019-07-20
      相关资源
      最近更新 更多