Search code examples
ioscore-datansfetchrequestfaultnsfetchedresultscontroller

CoreData ordered relationships - batch unfaulting using NSFetchRequest


Background - Batch unfaulting:
NSFetchRequest allows batch unfault - for example, use a query of 1000 results, it would bring all as faults, then it would unfault X objects at a time (i.e. index 0-20, then 21-40, etc)

This behavior is great when used in NSFetchResultsController for a UITableViewDataSource, and it allows fast UI scrolling as it doesn't unfault objects one-by-one.

Now to my problem:
I'm using ordered relationships for lists of objects, let's say Posts.

Since a Post may appear on a lot of lists on my model, I can't store its index in every lists on Post entity and use it as a param for ordering results.

As for now, I haven't found a way for NSFetchRequest to fetch according to this order, so I can't use its batch unfaulting. So I'm addressing to the relationship with an index, and I end up unfaulting one-by-one, which causes bumpy scrolling.

Is there any way for NSFetchResultsController to fetch according to order relationships? Or, is there a batch unfaulting API which isn't private?


Solution

  • Currently I have a solution for that, but not a clean one:
    I want to batch unfault by groups of 20, in an ordered relationship.

    So, every time i address an index that its index divides by 20 (index % 20 == 0), I use a new NSFetchRequest for the next 20 and unfault them by calling a common field name.

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        if (indexPath.row % 20 == 0) {
            NSArray *objectsToUnfault = [[someObject.orderedRelationship array] subarrayWithRange:NSMakeRange(indexPath.row, MIN(20, [someObject.orderedRelationship count] - indexPath.row))];
            // It's important to hold on this array, otherwise objects fault back
            self.lastPrefetch = [self preFetchEntityOfClass:[Post class] faultObjects:objectsToUnfault error:&error];
        }
    //... continue and create cell
    }
    
    
    - (NSArray *)preFetchEntityOfClass:(Class)entityClass faultObjects:(NSArray*)objects error:(NSError **)error {
        NSEntityDescription *entityDescription = [NSEntityDescription entityForName:NSStringFromClass(entityClass) inManagedObjectContext:self.managedObjectContext];
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF in %@", objects];
        NSFetchRequest *request = [[NSFetchRequest alloc] init];
        [request setEntity:entityDescription];
        [request setPredicate:predicate];
        [request setFetchBatchSize:MIN([objects count], 20)];
        NSArray *results = [self.managedObjectContext executeFetchRequest:request error:error];
        // I assume all my objects has this field "uid"
        NSArray *resultsUid = [results valueForKey:@"uid"]; //Batch unfaulting (results is a BatchFaultArray, private class type)
        if ([resultsUid count] != [results count]) {
            NSLog(@"Error: wrong count of uids"); //We need to use resultsUid, to avoid compiler opt
        }
        return results;
    }