问题
I'm seeing a situation where a NSFetchRequest returns a different number of objects depending on whether it's executed directly through the NSManagedObjectContext or as part of building a NSFetchedResultsController.
Sample code:
- (void)setupResultsController {
NSError *error = nil;
NSManagedObjectContext *ctx = [[DataManager sharedInstance] mainObjectContext];
// Create a fetch request and execute it directly
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [Song entityInManagedObjectContext:ctx];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"section" ascending:YES];
NSSortDescriptor *nameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
NSArray *sortDescriptors = @[sortDescriptor, nameDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
NSArray *debugResults = [ctx executeFetchRequest:fetchRequest error:&error];
NSLog(@"Count from context fetch: %lu", (unsigned long)debugResults.count);
// Use the request to populate a NSFetchedResultsController
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:ctx
sectionNameKeyPath:@"section"
cacheName:@"Detail"];
[aFetchedResultsController performFetch:&error];
NSLog(@"Count from results controller fetch: %lu", (unsigned long)[[aFetchedResultsController fetchedObjects] count]);
_songResultsController = aFetchedResultsController;
}
Executing the above results in log messages:
2020-01-10 11:05:07.892772-0500 asb7[12985:105052] Count from context fetch: 10
2020-01-10 11:05:07.893259-0500 asb7[12985:105052] Count from results controller fetch: 9
The difference between the two fetches is that the NSFetchedResultsController is missing the most recently added object. An extreme oddity about this is that, after running the application some seemingly random number of times, the counts start to agree and the new object is fetched.
Edit:
The results become consistent if I pass nil
as the cache name or if I remove the second sort descriptor. Obviously these cause undesirable behavior changes but may be clues.
It seems that the NSFetchedResultsController is seeing a stale cache as being valid. Changing a sort descriptor invalidates the cache however updating the persistent store file should invalidate it but apparently does not in this case.
After a bit more experimenting, I have an explanation...if not a solution. Adding new objects does not change the modification date of my .sqlite file. It updates the .sqlite-shm and .sqlite-wal but I'll guess those aren't considered when judging whether to use the cache. Using touch
from a terminal session makes the problem go away for the next launch.
(Xcode 10.1, macOS 10.13.6, deployment target 10.3, iOS 12.1 simulator and 10.3.2 device)
Another edit:
I've uploaded a zipped project directory that demonstrates the problem at https://github.com/PhilKMills/CacheTest
What I get is: first run, 3 records for both fetches; second run, 6 and 3. I see it as entirely possible that this depends on my particular software versions but I'm not in a position to upgrade at the moment. Other people's results would be most interesting.
Note: without a FRC delegate being assigned, the problem does not appear.
回答1:
I suspected that the issue related to the use of the FRC cache, though I was uncertain why that might be. One workaround would therefore be to abandon using the FRC cache. However, that's not ideal.
After further experimentation by the OP, it seems the problem relates to the timestamp on the .sqlite
file associated with the persistent store. By inference(*), if the FRC detects that the timestamp has changed, it realises that its cache might be out of date, and rebuilds it - thereby detecting the newly added objects. However, because CoreData by default uses SQLite's "WAL journal mode" (see here and here), database updates are written not to the .sqlite
file, but instead to a separate .sqlite-wal
file: the timestamp on the .sqlite
file is consequently not changed, even though the database has been updated. The FRC continues to use its cache, unaware of the additional objects (or indeed other changes).
A second workaround is therefore to abandon using SQLite's WAL journal mode. This can be achieved by specifying the required journal mode when loading the persistent store, using
NSSQLitePragmasOption:@{@"journal_mode":@"DELETE"}
see here.
(*) In fact, this is documented here:
“the controller tests the cache to determine whether its contents are still valid. The controller compares the current entity name, entity version hash, sort descriptors, and section key-path with those stored in the cache, as well as the modification date of the cached information file and the persistent store file.”
来源:https://stackoverflow.com/questions/59685471/nsfetchedresultscontroller-finds-wrong-number-of-objects