views:

241

answers:

2

EDIT: see below for a fix, but I don't understand why the fix works. :)

Code:

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

NSEntityDescription *entity = [NSEntityDescription entityForName:@"A"
                                          inManagedObjectContext:moc];
[fetchRequest setEntity:entity];

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"id" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"somePredicate", someObject];
[fetchRequest setPredicate:predicate];

frc = [[NSFetchedResultsController alloc]
       initWithFetchRequest:fetchRequest
       managedObjectContext:moc
       sectionNameKeyPath:@"recency"
       cacheName:@"frc"];
[fetchRequest release];

frc.delegate = self;

NSError *error;
BOOL success = [frc performFetch:&error];
if (!success) {
    NSLog(@"error: %@", error);
}

for (A *a in [frc fetchedObjects]) {        
    [someMutableArray addObject:a.b];
    [someMutableArray addObject:a];
}

Data model:

A and B are entities. A has mandatory to-one relation to B. B has inverse optional to-many relation to A.

Above in English:

Initialize a NSFetchedResultsController to grab some data to power a tableview. After initial fetch, set the data aside for some processing.

Now, later, I try to do this:

id object = [someMutableArray objectAtIndex:someIndex];
NSLog(@"%@", object);

if ([object isMemberOfClass:[B class]]) {
    someVar = object.propertyFromB; // problem
} else if ([object isMemberOfClass:[A class]]) {
    someVar = object.propertyFromA;
}

Question/problem: the line indicated with "problem" crashes. (EDIT: See below for resolution, but would still like an explanation.)

The NSLog call above yields:

2010-01-30 14:47:14.433 app[22618:20b] <B: 0xf7f750> (entity: B; id: 0xf7ba70 <x-coredata://B01FEC86-14D6-4973-BFDB-EDE4AFD24FDC/B/p4> ; data: <fault>)
2010-01-30 14:47:14.438 app[22618:20b] <A: 0xf7e360> (entity: A; id: 0xf35820 <x-coredata://B01FEC86-14D6-4973-BFDB-EDE4AFD24FDC/A/p6> ; data: {
    prop1 = value1;
    prop2 = value2;
    ... etc ...
})

I.e by the problematic line, if the object was of type A, it has been faulted and is available in memory, but if it is B, it's a fault.

My understanding is that the "problem" line should fire the fault and fetch the data from store, but this is not happening. I would like to understand/debug why. I have tried inserting willAccessKey/didAccessKey calls around this. I also tried to set setRelationshipKeyPathsForPrefetching:"b" on the fetch request. Neither worked.

My hypothesis is that since I'm somewhat abusing the NSFetchedRequestController results, the faulting engine gets confused along the way and doesn't fetch the fault when it's supposed to. So I guess a bruteforce way would be to create a new manual fetch request to fetch the related B object at the right time. But is there a better way?

EDIT:

I managed to fix the problem, but I don't understand why the fix works.

The problem was that object B had a property "description" that I had defined, but that collides with NSObject's built-in name. Xcode always gave me warnings, but I ignored them because I thought "description" internal property/method is only used for dumping strings to console and the like, not internal processing.

The problem disappeared after I made a new version of my model, renaming "description" to something else. All the faulting started to work as expected.

I don't understand, though, what is going on. Is Core Data using the objects' "description" method for some internal introspection?

+4  A: 

From Core Data Programming Guide

You are discouraged from overriding description—if this method fires a fault during a debugging operation, the results may be unpredictable—and initWithEntity:insertIntoManagedObjectContext:. You should typically not override the key-value coding methods such as valueForKey: and setValue:forKeyPath:.

-description is a method in NSObject that returns a string representation of your object. In the line NSLog(@"%@", object), -description is used to get the string that you see in the console. Key-value coding will end up using the method to get the property for the description attribute. This causes a whole lot of confusion to Core Data.

The programming guide is being generous when it says "discouraged". They really mean "Yeah, it's going to break your stuff."

That link also has a good list of other methods that will break your stuff if you override them.

Giao
That all makes sense and I generally try to refrain from overriding such "well-known" methods. My bad for not realizing how sensitive "description" was.
Jaanus
A: 

you need to treat description as a reserved word. That is the issue you are having. You should have received a warning when you tried to have a property called description.

Marcus S. Zarra
I did indeed receive a warning. I thought I'd ignore it at the time since I didn't have any problems in the beginning when I started creating the objects. Turns out it was a bad idea :)
Jaanus
Yeah, ignoring warnings is a bad idea. Every developer should strive for a warning free build! Perhaps even set 'warnings as errors'.
Hunter
Is there a best practice for modeling entities that have a "description" attribute? Do people tend to call it "desc" or something?
Hugh
Not really a best practice but desc seems to be the most common that I have seen.
Marcus S. Zarra