开发者

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 a UITableView, 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 an nil 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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜