NSFetchedResultsController cache mismatch again
This is another case where calling performFetch
on an NSFetchedResultController
breaks with an exception saying:
FATAL ERROR: The persistent cache of section information does not match the current configuration. You have illegally mutated the NSFetchedResultsController's fetch request, its predicate, or its sort descriptor without either disabling caching or using +deleteCacheWithName:
Except I haven't done any of this. To understand the problem, look at the predicate:
predicate: (collection.identifier == ";root" AND doc.status == 0);
Now look at the mismatched cache contents:
cached objects were: (
"MyDocument 'PL00002.jpeg' in set '/' (#2147483647)",
"MyDocument 'PL00003.jpeg' in set '/' (#2147483647)",
"MyDocument 'PL00003.pdf' in set '/' (#2147483647)",
"MyDocument 'PL00002.pdf' in set '/' (#2147483647)"
)
fetched objects are: (
"MyDocument 'PL00002.jpeg' in set '/' (#2147483647)",
"MyDocument 'PL00003.jpeg' in set '/' (#2147483647)",
"MyDocument 'PL00004.jpeg' in set '/' (#2147483647)",
"MyDocument 'PL00003.pdf' in set '/' (#2147483647)",
"MyDocument 'PL00002.pdf' in set '/' (#2147483647)",
"MyDocument 'PL00004.pdf' in set '/' (#2147483647)"
)
Indeed, there are two new matching objects. Th开发者_如何学运维e reasons is that the two new objects (PL00004.jpeg
and PL00004.pdf
) had previously their status
property to a value <>0 and I changed their status
back to 0 in code because they started to satisfy some external condition (not triggered by a user action).
For completeness, here is the code that triggers the exception:
- (void) reloadData
{
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
abort(); // I have actually better error handling
}
[_gridView reloadData];
}
also note:
- no other
NSFetchedResultController
uses a cache with the same name _gridView
is not aUITableView
, though it also displays a collection of entities. This detail should not be relevant.- none of the
NSFetchedResultController
delegate methods are ever called, esp not when I change the status of my two objects to 0 - everything runs OK if I empty the cache before
performFetch:
(or with no caching). - I call this
reloadData
method on my controller at the end of my periodical check for that external condition I mentioned above. - The
NSFetchedResultController
is created with annil
sectionNameKeyPath
. - As explained I never change
fetchedResultsController
: not its predicate, not its fetch request, not its sort order, nothing.
I don't think I should get this exception simply because new objects now satisfy the predicate. Deleting the cache avoids the exception, but as long as I don't understand why I should do that, I don't consider that as a fix.
Any idea what I miss here?
Edit: here is the requested information:
The code to set up the NSFetchedResultController
. When the user chooses a collection, this is called:
- (void) displayCollection:(Collection *)aSet
{
self.currentCollection = aSet;
NSFetchRequest *fetchRequest = [[self fetchedResultsController] fetchRequest];
NSString *collectionIdentifier = @";root"; // no selection is like "select root collection";
if (aSet) {
collectionIdentifier = aSet.identifier;
}
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"collection.identifier == %@ and doc.status == %d", collectionIdentifier, upToDate];
[fetchRequest setPredicate:predicate];
[NSFetchedResultsController deleteCacheWithName:cacheName];
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
abort(); // whatever
}
[_gridView reloadData];
}
fetchedResultsController
was initialized thus:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"DocInCollection" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"collection.identifier == %@ and doc.status == %d", @";root", upToDate];
[fetchRequest setPredicate:predicate];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"position" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:cacheName];
The code that is called when doc.status
is updated:
- (void) setUpToDate
{
self.status = [NSNumber numberWithInt:upToDate];
NSError *error = nil;
[[self managedObjectContext] save:&error];
if (error) { } // whatever
[[self managedObjectContext] processPendingChanges];
}
cacheName
is a global const NSString
, upToDate
is an enum constant (0).
Finally the model is a bit complex because of other entities that are used by the application. Here is a simplified version:
model http://emberapp.com/jdmuys/images/untitled-1/sizes/m.png
I would look at your sectionNameKeyPath
and it's relationship, if any, to the attribute being mutated. It would seem from the error message that your sections are changing in a way the controller does not like.
Not sure if it is related at all but I would be nervous of any statement that has an unescaped semicolon in it. That predicate bit of ";root"
gives me an itch. Of course, any problems with it should show up at compile time but still...
Update:
Out of morbid curiosity I banged together a test data model and some code using the above. The following will not even compile:
NSManagedObject *cMO=[NSEntityDescription insertNewObjectForEntityForName:@"Collection" inManagedObjectContext:self.moc];
NSManagedObject *dMO=[NSEntityDescription insertNewObjectForEntityForName:@"Doc" inManagedObjectContext:self.moc];
[cMO setValue:@";root" forKey:@"identifier"];
[dMO setValue:[NSNumber numberWithInt:0] forKey:@"status"];
[cMO setValue:dMO forKey:@"doc"];
NSFetchRequest *fetch=[[NSFetchRequest alloc] init];
[fetch setEntity:[NSEntityDescription entityForName:@"Collection" inManagedObjectContext:self.moc]];
NSPredicate *p=[NSPredicate predicateWithFormat:@"identifier==";root" AND doc.status==0"];
[fetch setPredicate:p];
NSArray *a=[self performFetch:fetch];
NSLog(@"%@",a);
Changing the predicate to:
NSPredicate *p=[NSPredicate predicateWithFormat:@"identifier == ';root' AND doc.status == 0"];
... compiles and works properly. I suggest you change the ";root"
to ';root'
at least for troubleshooting.
精彩评论