I have a NSFetchedResultsController used to display data in a table view (22117 rows). The fetchBatchSize is set to 20 on the fetchRequest. It works as expected: only 20 rows are fully populated when the table view is loaded. SQL statements:
2015-06-12 17:59:55.526 BoulderFinder[2228:1220755] CoreData: sql: SELECT 0, t0.Z_PK FROM ZBOULDER t0 ORDER BY t0.ZNORMALIZEDNAME
2015-06-12 17:59:55.533 BoulderFinder[2228:1220755] CoreData: annotation: sql connection fetch time: 0.0070s
2015-06-12 17:59:55.534 BoulderFinder[2228:1220755] CoreData: annotation: total fetch execution time: 0.0075s for 22117 rows.
2015-06-12 17:59:55.534 BoulderFinder[2228:1220755]
2015-06-12 17:59:55.540 BoulderFinder[2228:1220755] CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZBOULDERDESCRIPTION, t0.ZFIRSTCLIMBERFIRSTNAME, t0.ZFIRSTCLIMBERLASTNAME, t0.ZGRADE, t0.ZIMAGE, t0.ZLATITUDE, t0.ZLONGITUDE, t0.ZNAME, t0.ZNORMALIZEDAREANAME, t0.ZNORMALIZEDCIRCUITNAME, t0.ZNORMALIZEDGRADE, t0.ZNORMALIZEDNAME, t0.ZNUMBERINCIRCUIT, t0.ZAREA, t0.ZCIRCUIT FROM ZBOULDER t0 WHERE t0.Z_PK IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ORDER BY t0.ZNORMALIZEDNAME LIMIT 20
2015-06-12 17:59:55.541 BoulderFinder[2228:1220755] CoreData: annotation: sql connection fetch time: 0.0010s
2015-06-12 17:59:55.541 BoulderFinder[2228:1220755] CoreData: annotation: total fetch execution time: 0.0014s for 20 rows.
However, I have another view to display the same entities in a different order (only a few entities filtered through a predicate). I am not using a NSFetchedResultsController in this view. If I load this view, where about 59 objects are loaded in the managedObjectContext, and then re-load the other table view, the NSFetchedResultsController now loads all rows (22117) by batches of 20 rows. Here the SQL statements (only the first ones):
2015-06-12 18:00:23.315 BoulderFinder[2228:1220755] CoreData: sql: SELECT 0, t0.Z_PK FROM ZBOULDER t0 ORDER BY t0.ZNORMALIZEDNAME
2015-06-12 18:00:23.322 BoulderFinder[2228:1220755] CoreData: annotation: sql connection fetch time: 0.0067s
2015-06-12 18:00:23.322 BoulderFinder[2228:1220755] CoreData: annotation: total fetch execution time: 0.0070s for 22117 rows.
2015-06-12 18:00:23.323 BoulderFinder[2228:1220755] CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZBOULDERDESCRIPTION, t0.ZFIRSTCLIMBERFIRSTNAME, t0.ZFIRSTCLIMBERLASTNAME, t0.ZGRADE, t0.ZIMAGE, t0.ZLATITUDE, t0.ZLONGITUDE, t0.ZNAME, t0.ZNORMALIZEDAREANAME, t0.ZNORMALIZEDCIRCUITNAME, t0.ZNORMALIZEDGRADE, t0.ZNORMALIZEDNAME, t0.ZNUMBERINCIRCUIT, t0.ZAREA, t0.ZCIRCUIT FROM ZBOULDER t0 WHERE t0.Z_PK IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ORDER BY t0.ZNORMALIZEDNAME LIMIT 79
2015-06-12 18:00:23.324 BoulderFinder[2228:1220755] CoreData: annotation: sql connection fetch time: 0.0009s
2015-06-12 18:00:23.324 BoulderFinder[2228:1220755] CoreData: annotation: total fetch execution time: 0.0013s for 20 rows.
2015-06-12 18:00:23.324 BoulderFinder[2228:1220755] CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZBOULDERDESCRIPTION, t0.ZFIRSTCLIMBERFIRSTNAME, t0.ZFIRSTCLIMBERLASTNAME, t0.ZGRADE, t0.ZIMAGE, t0.ZLATITUDE, t0.ZLONGITUDE, t0.ZNAME, t0.ZNORMALIZEDAREANAME, t0.ZNORMALIZEDCIRCUITNAME, t0.ZNORMALIZEDGRADE, t0.ZNORMALIZEDNAME, t0.ZNUMBERINCIRCUIT, t0.ZAREA, t0.ZCIRCUIT FROM ZBOULDER t0 WHERE t0.Z_PK IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ORDER BY t0.ZNORMALIZEDNAME LIMIT 79
2015-06-12 18:00:23.326 BoulderFinder[2228:1220755] CoreData: annotation: sql connection fetch time: 0.0012s
2015-06-12 18:00:23.326 BoulderFinder[2228:1220755] CoreData: annotation: total fetch execution time: 0.0015s for 20 rows.
...
The only workaround I have found is to call resetContext before setting up the NSFetchedResultsController.
Below the relevant code:
- (void)viewDidLoad
{
[super viewDidLoad];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:[BDBBoulder entityName]];
NSSortDescriptor *normalizedNameSortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"normalizedName" ascending:YES];
fetchRequest.sortDescriptors = @[normalizedNameSortDescriptor];
fetchRequest.fetchBatchSize = 20;
// [self.fetchedResultsController.managedObjectContext reset];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.boulderSearch.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController.delegate = nil;
// Perform fetch
NSError *error = nil;
[self.fetchedResultsController performFetch:&error];
if (error) {
NSLog(@"Unable to perform fetch.");
NSLog(@"%@, %@", error, error.localizedDescription);
}
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
BDBBoulder *boulder = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [NSString stringWithFormat:@"%@ / %@", boulder.normalizedName,boulder.grade];
cell.detailTextLabel.text = [NSString stringWithFormat:@"%@ / %@", boulder.normalizedCircuitName,boulder.normalizedAreaName];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSArray *sections = [self.fetchedResultsController sections];
id<NSFetchedResultsSectionInfo> sectionInfo = [sections objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"allBouldersCell" forIndexPath:indexPath];
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
I cannot understand this behavior.
EDIT:
I have performed the test again with a simplified app to be sure of the behavior. I have 2 VCs: - in the first one, I fetch all objects (22117) with a fetchBatchSize of 20 and access to the first element of the results - in the second one, I fetch 59 objects and modify their transient property
fetchRequest.includesPendingChanges = YES
If I load the first VC, only the first 20 rows are fully loaded:
Now, if I load the second VC and then the first VC, then all rows are loaded by batches of 20.
However, if I conduct the same test without modifying the transient property, then only the first 20 rows are fully loaded.
Finally, if fetchRequest.includesPendingChanges = NO
to ignore the pending changes, then it works as expected in all cases.
Strange but this is how it works. Thank pbasdf.