Why my NSFetchedResultsController loads all rows?

323 views Asked by At

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: storyboard

1

There are 1 answers

0
TonioGA On BEST ANSWER

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:

2015-06-16 23:13:45.520 BoulderFinder[946:271012] CoreData: sql: SELECT 0, t0.Z_PK FROM ZBOULDER t0 
2015-06-16 23:13:45.528 BoulderFinder[946:271012] CoreData: annotation: sql connection fetch time: 0.0074s
2015-06-16 23:13:45.528 BoulderFinder[946:271012] CoreData: annotation: total fetch execution time: 0.0081s for 22117 rows.
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  (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)   LIMIT 20
2015-06-16 23:13:45.532 BoulderFinder[946:271012] CoreData: annotation: sql connection fetch time: 0.0012s
2015-06-16 23:13:45.532 BoulderFinder[946:271012] CoreData: annotation: total fetch execution time: 0.0021s for 20 rows.

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.