获取一对多核心数据关系会在第一次返回正确的对象,但在其他时间返回空集

时间:2012-05-09 18:47:34

标签: objective-c ios core-data

我有一个对象,Workout,它与一个对象有一对多的关系,练习。

模型图:http://i.imgur.com/q1Mfq.png

当我创建一个Workout对象时,我通过循环

向它添加三个Exercise对象
[self addExercisesObject:exercise]

然后保存我的托管对象上下文。然后,从我的控制器显示锻炼,我可以成功获取锻炼及其练习(带有获取请求),如调试器中的输出所示:

Printing description of self->_savedWorkout:
<Workout: 0x6c5a990> (entity: Workout; id: 0x6e46e00 <x-coredata://EA417EAA-101A-4F04-8276-3C4A6CDF094D/Workout/p1> ; data: {
bodyweight = nil;
date = "2012-05-09 16:59:43 +0000";
exercises =     (
    "0x6e3c870 <x-coredata://EA417EAA-101A-4F04-8276-3C4A6CDF094D/Exercise/p3>",
    "0x6e3eaf0 <x-coredata://EA417EAA-101A-4F04-8276-3C4A6CDF094D/Exercise/p2>",
    "0x6e36820 <x-coredata://EA417EAA-101A-4F04-8276-3C4A6CDF094D/Exercise/p1>"
);
isCompleted = 0;
workoutId = 1;
workoutPlan = "0x6e6c980 <x-coredata://EA417EAA-101A-4F04-8276-3C4A6CDF094D/WorkoutPlan/p1>";
})

到目前为止一切顺利。但是,如果我在我的模拟器中关闭我的应用程序并再次启动它并在同一视图中执行相同的获取请求,则锻炼看起来像这样:

Printing description of self->_savedWorkout:
<Workout: 0x6ea8ff0> (entity: Workout; id: 0x6e8f9e0 <x-coredata://EA417EAA-101A-4F04-8276-3C4A6CDF094D/Workout/p1> ; data: {
bodyweight = nil;
date = "2012-05-09 16:59:43 +0000";
exercises =     (
);
isCompleted = 0;
workoutId = 1;
workoutPlan = "0x6c8a130 <x-coredata://EA417EAA-101A-4F04-8276-3C4A6CDF094D/WorkoutPlan/p1>";
})

它似乎取得了相同的锻炼对象,但现在练习是一个空集。实际上,在获取请求之后,练习首先看起来像这样:

exercises = "<relationship fault: 0x8a93100 'exercises'>";

但是一旦我做了:

for (Exercise *exercise in self.savedWorkout.exercises)

self.savedWorkout.exercises解析为空集。我不会在我的应用程序的任何部分编辑锻炼。

我的提取请求是通过我的Workout类中的此方法生成的:

- (Workout *)getLatestWorkout
{
  self.model = [[self.managedObjectContext persistentStoreCoordinator] managedObjectModel];

  NSFetchRequest *fetchRequest = [self.model fetchRequestTemplateForName:@"getLatestWorkout"];

  NSError *error = nil;
  NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
  if ([results count] == 1) {
    return [results objectAtIndex:0];
  }
  return nil;
}

我使用Xcode的GUI工具制作了获取请求模板。它获取isCompleted == 0的所有Workout对象。您可以看到它每次都获取相同的对象,因为在两个调试器输出中,exercise的x-coredata路径是相同的。

更新:我检查了我的SQLite数据库。锻炼表中有一个锻炼,锻炼表中有三个锻炼。

任何想法发生了什么?

编辑:创建下面发布的对象的代码

- (void)storeUserSettings
{
  // get the file path if it exists
  NSString *path = [[NSBundle mainBundle] pathForResource:@"userSettings" ofType:@"plist"];

  // create it if it doesn't
  if (path == nil) {
    path = [NSString stringWithFormat:@"%@%@", 
            [[NSBundle mainBundle] bundlePath], @"/userSettings.plist"];
  }

  // and write the new settings to file
  [self.userSettings writeToFile:path atomically:YES];

  // load managed object context
  [self loadMOC];

  WorkoutPlan *currentPlan = [[WorkoutPlan alloc] getActiveWorkoutPlan];

  [currentPlan setManagedObjectContext:self.managedObjectContext];

  // if user has no plan or is changing plans, create new plan and first workout
  if (currentPlan == nil || 
      ([self.userSettings valueForKey:@"plan"] != currentPlan.planId)) {

    // create a workoutPlan object
    WorkoutPlan *workoutPlan = [[WorkoutPlan alloc] initWithEntity:
                                [NSEntityDescription entityForName:@"WorkoutPlan" 
                                            inManagedObjectContext:self.managedObjectContext] 
                                    insertIntoManagedObjectContext:self.managedObjectContext];

    // set attributes to values from userSettings and save object
    [workoutPlan createWorkoutPlanWithId:[self.userSettings valueForKey:@"plan"] 
                                schedule:[self.userSettings valueForKey:@"schedule"]
                             dateStarted:[self.userSettings valueForKey:@"nextDate"]];
  }
  // if user is just changing schedule, update schedule of current plan
  else if (![currentPlan.schedule isEqualToString:[self.userSettings valueForKey:@"schedule"]]) {
    [currentPlan setSchedule:[self.userSettings valueForKey:@"schedule"]];

    [currentPlan saveMOC];
  }
}

- (void)loadMOC
{
  AppDelegate *delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
  self.managedObjectContext = delegate.managedObjectContext;
  self.model = [[self.managedObjectContext persistentStoreCoordinator] managedObjectModel];
}

- (void)createWorkoutPlanWithId:(NSNumber *)planId schedule:(NSString *)schedule 
                    dateStarted:(NSDate *)dateStarted
{ 
  [self deactivateCurrentPlan];

  // set workout plan attributes
  [self setPlanId:planId];
  [self setIsActive:[NSNumber numberWithBool:YES]];
  [self setSchedule:schedule];
  [self setDateStarted:dateStarted]; 

  // create first workout and add to workout plan

  Workout *firstWorkout = [NSEntityDescription
                          insertNewObjectForEntityForName:@"Workout"
                          inManagedObjectContext:self.managedObjectContext];

  [firstWorkout setManagedObjectContext:self.managedObjectContext];

  [firstWorkout createFirstWorkoutForPlan:self onDate:dateStarted];

  [self addWorkoutsObject:firstWorkout];

  [self saveMOC];
}

- (void)createFirstWorkoutForPlan:(WorkoutPlan *)plan onDate:(NSDate *)startDate
{
  // set workout attributes
  [self setDate:startDate];
  [self setIsCompleted:[NSNumber numberWithBool:NO]];
  [self setWorkoutId:[NSNumber numberWithInt:1]];

  NSArray *exerciseList = [self getExercisesForWorkout:self inPlan:plan];

  // iterate over exercises in spec and create them
  for (NSDictionary *exerciseSpec in exerciseList) 
  {
    // create a exercise MO
    Exercise *exercise = [NSEntityDescription
                          insertNewObjectForEntityForName:@"Exercise"
                          inManagedObjectContext:[plan managedObjectContext]];

    [exercise setManagedObjectContext:[plan managedObjectContext]];
    [exercise createExerciseForWorkout:self withSpec:exerciseSpec];

    // add exercise to workout object
    [self addExercisesObject:exercise];
  }

}

- (void)createExerciseForWorkout:(Workout *)workout withSpec:exerciseSpec
{
  // set exercise attributes
  self.exerciseId = [exerciseSpec valueForKey:@"id"];
  self.isPersonalRecord = [NSNumber numberWithBool:NO];

  NSArray *sets = [exerciseSpec valueForKey:@"sets"];
  int i = 1;
  for (NSNumber *setReps in sets)
  {
    // create a set MO
    Set *set = [NSEntityDescription
                          insertNewObjectForEntityForName:@"Set"
                          inManagedObjectContext:[workout managedObjectContext]];

    [set setManagedObjectContext:[workout managedObjectContext]];

    // set set attributes
    set.order = [NSNumber numberWithInt:i];
    set.repetitions = setReps;
    set.weight = [exerciseSpec valueForKey:@"default_weight"]; 

    // add set to exercise object
    [self addSetsObject:set];
    i++;
  }
}

3 个答案:

答案 0 :(得分:1)

我有类似的问题。当应用程序运行时,父子关系起作用,但在重新启动后,仅检索到最新的子记录。

我正在添加这样的孩子:

  • 创建子记录
  • 设置子项的父属性,设置子项的其他属性 属性
  • 使用父级的add方法
  • 将孩子添加到父级

我发现如果我这样做就修好了:

  • 创建子记录
  • 使用父级的add方法
  • 将孩子添加到父级
  • 设置子项的父属性,设置子项的其他属性 属性

答案 1 :(得分:0)

核心数据很复杂。可能有很多事情需要检查,任何可能导致问题的事情。

您使用了多少个MOC?你是如何储蓄的?还有更多问题......

我建议在启动应用程序时在EditScheme中打开SQL调试标志(-com.apple.CoreData.SQLDebug 1)作为参数。

运行您的代码,看看实际发生了什么。

关系会在获取时解析为错误,除非您在获取请求中覆盖它。

如果您在父/子关系中使用多个MOC,则从子级到父级的保存只是将数据放入父级,它并不真正保存它。如果使用UIManagedDocument,那就是一组完全不同的问题......

我希望这听起来并不严厉。准备好为Core Data问题提供大量信息,除了“这不是保存,这是一些调试输出。”

基本上,CoreData的工作方式取决于如何创建堆栈,是否使用UIManagedDocument,多个线程,如何创建对象,如何保存它们,获取请求的选项以及更多的东西。

实际上并不复杂,但有很多自定义和特殊情况取决于它的使用方式。

修改

发布创建对象/关系的代码。

此外,尝试使用手动提取请求而不是模板进行提取。当您查看数据库中的数据时,您是否看到了适当设置关系的外键?

在启用调试的情况下再次运行它以查看SQL正在执行的操作。这对您自己的调试输出更有价值。

答案 2 :(得分:0)

我遇到了同样的问题,但我的模型非常复杂。我的应用程序在启动时创建实体和关系(如果它们尚不存在)。如果它们被创建并且我没有退出应用程序,我就能够获取具有多对多关系的实体并查看相关对象的正确计数。如果我退出我的应用程序并重新启动它(它现在知道它不必创建一组默认数据),那么关系将返回一个空集。我无法理解。

编辑:我发现我的问题与有序集合关系有关。我不得不使用Category来创建一个解决方法(在堆栈溢出时找到)以将新条目插入到有序集中。所以我猜这与它有关。