views:

276

answers:

1

I have three Classes for which I am extending NSManagedObject (I know this isn't required but I wanted the property notation). Theses Classes are QuestionSet, Question and Answer. My data model is setup so that there's a many to many between them like so: QuestionSet <<->> Question <<->> Answer. I load up everything from a server and it save successfully meaning I look in the .db file and it's all how I expect, if I immediately NSLog the array questionsets they look great and if I go take a quiz it works great.

However if I NSLog that array anywhere but right after where I load them from the server, it crashes my app. The quiz still runs fine so I know the data is in there and in the expected format. Does this have something to do with Core Data clearing out space and then my relationships don't 'lazy loaded' when I'm just trying to log them? Sorry, still wrapping my head around core data.

This bit works, I load from the server and at the bottom I 'loadTrivia' and log what I get. The issue comes if at sometime later during the course of the app execution, an NSLog of the questionsets will fail.

- (void)loadQuestionSetFromNetworkWithID:(NSNumber *)p_id andTitle:(NSString *)p_title
{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:kTriviaQuestionSetURL, [p_id intValue]]]];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
NSString *json_string = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];

NSArray *questions = [[[sbjson objectWithString:json_string error:nil] valueForKeyPath:@"Q"] objectAtIndex:0];

NSError *error;

NSFetchRequest *questionsetRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *questionsetEntity = [NSEntityDescription entityForName:@"QuestionSet" inManagedObjectContext:[self managedObjectContext]];
NSPredicate *questionsetPredicate = [NSPredicate predicateWithFormat:@"id = %d", [p_id intValue]];

[questionsetRequest setEntity:questionsetEntity];
[questionsetRequest setPredicate:questionsetPredicate];

QuestionSet *qs = nil;
NSArray *questionSetObjects = [[self managedObjectContext] executeFetchRequest:questionsetRequest error:&error];
if (questionSetObjects == nil){
 // Handle errors
}
if ([questionSetObjects count] > 0)
 qs = [questionSetObjects objectAtIndex:0];
else
 qs = [NSEntityDescription insertNewObjectForEntityForName:@"QuestionSet" inManagedObjectContext:[self managedObjectContext]];

qs.id = p_id;
qs.title = p_title; 

for (int i = 0; i < [questions count]; i++)
{
 NSFetchRequest *questionRequest = [[NSFetchRequest alloc] init];
 NSEntityDescription *questionEntity = [NSEntityDescription entityForName:@"Question" inManagedObjectContext:[self managedObjectContext]];
 NSPredicate *questionPredicate = [NSPredicate predicateWithFormat:@"(id = %d)", [[[questions objectAtIndex:i] objectForKey:@"id"] intValue]];

 [questionRequest setEntity:questionEntity];
 [questionRequest setPredicate:questionPredicate];

 Question *q = nil;
 NSArray *questionObjects = [[self managedObjectContext] executeFetchRequest:questionRequest error:&error];
 if (questionObjects == nil){
  // Handle errors
 }
 if ([questionObjects count] > 0)
  q = [questionObjects objectAtIndex:0];
 else
  q = [NSEntityDescription insertNewObjectForEntityForName:@"Question" inManagedObjectContext:[self managedObjectContext]];

 q.id = [NSNumber numberWithInt:[[[questions objectAtIndex:i] objectForKey:@"id"] intValue]];
 q.question = [[questions objectAtIndex:i] objectForKey:@"text"];
 q.answer = [NSNumber numberWithInt:[[[questions objectAtIndex:i] objectForKey:@"ca"] intValue]];

 for (int j = 0; j < [[[questions objectAtIndex:i] objectForKey:@"A"] count]; j++)
 {
  NSFetchRequest *answerRequest = [[NSFetchRequest alloc] init];
  NSEntityDescription *answerEntity = [NSEntityDescription entityForName:@"Answer" inManagedObjectContext:[self managedObjectContext]];
  NSPredicate *answerPredicate = [NSPredicate predicateWithFormat:@"(id = %d)", [[[[[questions objectAtIndex:i] objectForKey:@"A"] objectAtIndex:j] objectForKey:@"id"] intValue]];

  [answerRequest setEntity:answerEntity];
  [answerRequest setPredicate:answerPredicate];

  Answer *a = nil;
  NSArray *answerObjects = [[self managedObjectContext] executeFetchRequest:answerRequest error:&error];
  if (answerObjects == nil){
   // Handle errors
  }
  if ([answerObjects count] > 0)
   a = [answerObjects objectAtIndex:0];
  else
   a = [NSEntityDescription insertNewObjectForEntityForName:@"Answer" inManagedObjectContext:[self managedObjectContext]];

  a.id = [NSNumber numberWithInt:[[[[[questions objectAtIndex:i] objectForKey:@"A"] objectAtIndex:j] objectForKey:@"id"] intValue]];
  a.answer = [[[[questions objectAtIndex:i] objectForKey:@"A"] objectAtIndex:j] objectForKey:@"text"];

  a.questions = [a.questions setByAddingObject:q];
  q.answers = [q.answers setByAddingObject:a];

  [answerRequest release];
 }

 q.questionsets = [q.questionsets setByAddingObject:qs];
 qs.questions = [qs.questions setByAddingObject:q];

 [questionRequest release];

}

[questionsetRequest release];

[[self managedObjectContext] save:&error];

[json_string release];

[self loadTrivia];
NSLog(@"After Load: %@", self.questionsets);
}

This function fetches all the QuestionSet objects from the db and stores the array in an ivar.

- (BOOL)loadTrivia
{
NSError *error;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"QuestionSet" inManagedObjectContext:[self managedObjectContext]];
[request setEntity:entity];
self.questionsets = [[self managedObjectContext] executeFetchRequest:request error:&error];
return YES;
}

Just for completeness, here are my description methods in my classes

from QuestionSet.h

- (NSString *)description
{
return [NSString stringWithFormat:@"\
  \n\tid: %@\
  \n\ttitle: %@\
  \n\tquestions: %@\n",
  self.id, 
  self.title, 
  self.questions];
}

from Question.h

- (NSString *)description
{
return [NSString stringWithFormat:@"\
  \n\tid: %@\
  \n\tanswer: %@\
  \n\tquestion: %@\
  \n\tanswers: %@\n", 
  self.id, 
  self.answer, 
  self.question, 
  self.answers];
}

from Answer.h

- (NSString *)description
{
return [NSString stringWithFormat:@"\
  \n\tid: %@\
  \n\tanswer: %@\n",
  self.id, 
  self.answer];
}
+1  A: 

You shouldn't subclass the description method (Link):

Although the description method does not cause a fault to fire, if you implement a custom description method that accesses the object’s persistent properties, this will cause a fault to fire. You are strongly discouraged from overriding description in this way.

I tested your loadTrivia code (with a different entityName) on my app and it worked fine. Did you have a look at the NSError (you should initialize it with =nil):

NSLog(@"Unresolved error %@, %@", error, [error userInfo]);

Also you should release the NSFetchRequest after execution in - (BOOL)loadTrivia.

Felix
I'd say that's pretty iron clad.
rob5408
Sorry more information. Both loadQuestionSetFromNetworkWithID:andTitle: and loadTrivia: were working for me, but I put it there in case someone assumed my error was there. Thanks for the help! Rob
rob5408
Glad I could help. What was the issue?
Felix
The only thing really causing the error was the NSLog call on the NSManagedObject with the custom description method, other than that the app worked exactly how I expected. After seeing that link you posted, I realized I just couldn't have my own. So it was simple really. The descriptions that NSManagedObject is providing seems to be working well enough for me anyway.
rob5408