Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Core Data child contexts not prefetching relationships

Tags:

ios

core-data

I activated the Core Data debugger -com.apple.CoreData.SQLDebug 1 in my app scheme and got the following results for an entity called Category with a relationship entity called Image:

FetchRequest on the main context:

NSFetchRequest *fr = [[NSFetchRequest alloc] initWithEntityName:@"Category"];
fr.relationshipKeyPathsForPrefetching = @[@"image"];

NSArray *results = [self.mainContext executeFetchRequest:fr error:nil];

for (Category *category in results) {

    NSLog(@"%@", category.image.width);
}

The console log shows no faults being fulfilled - the expected behavior since the image relationship was set for prefetching.

The same request on a child context:

NSManagedObjectContext *privateMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[privateMOC setParentContext:self.mainContext];

[privateMOC performBlock:^{

        NSFetchRequest *fr = [[NSFetchRequest alloc] initWithEntityName:@"Category"];
        fr.relationshipKeyPathsForPrefetching = @[@"image"];

        NSArray *results = [privateMOC executeFetchRequest:fr error:nil];

        for (Category *category in results) {

            NSLog(@"%@",category.image.width);
        }
    }];

In this case, the console shows Core Data fulfilling faults for each image (4 of them). Is this a bug, expected behavior or am I missing something please?

like image 528
RunLoop Avatar asked Dec 01 '15 10:12

RunLoop


1 Answers

Experiment:

I have replicated your scenario in a test app and come to the same conclusion: the prefetching is not happening in the background thread. Log:

******************************** FOREGROUND **************************************

CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZTIMESTAMP, t0.ZTITLE, t0.ZIMAGE FROM ZCATEGORY t0 
CoreData: annotation: sql connection fetch time: 0.0004s
CoreData: annotation: Bound intarray values.
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZURL FROM ZIMAGE t0 WHERE  t0.Z_PK IN (SELECT * FROM _Z_intarray0)  
CoreData: annotation: sql connection fetch time: 0.0006s
CoreData: annotation: total fetch execution time: 0.0010s for 4 rows.
CoreData: annotation: Prefetching with key 'image'.  Got 4 rows.
CoreData: annotation: total fetch execution time: 0.0035s for 4 rows.

******************************** BACKGROUND **************************************

CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZTIMESTAMP, t0.ZTITLE, t0.ZIMAGE FROM ZCATEGORY t0 
CoreData: annotation: sql connection fetch time: 0.0003s
CoreData: annotation: total fetch execution time: 0.0005s for 4 rows.

Analysis:

According to the documentation of NSFetchRequest, it is explained that the prefetching is offered to address specific performance challenges. It is described as follows (my emphasis):

Prefetching allows Core Data to obtain related objects in a single fetch (per entity), rather than incurring subsequent access to the store for each individual record as their faults are tripped. For example, given an Employee entity with a relationship to a Department entity, if you fetch all the employees then for each print out their name and the name of the department to which they belong, it may be that a fault has to be fired for each individual Department object (for more details, see Core Data Performance in Core Data Programming Guide). This can represent a significant overhead. You could avoid this by prefetching the department relationship in the Employee fetch...

From the wording it follows that this is not a necessary device to improve performance. This would explain why it is not implemented on a background thread: the performance issue described in the discussion seems to indicate a typical scenario involving UI updates. To my mind, this clarifies the intent of this feature.

If you are already on a background thread, this kind of performance tuning is surely less critical and the usual faulting mechanism can handle the necessary optimization adequately. The property relationshipKeyPathsForPrefetching will thus revert to its default value, an empty array.

like image 123
Mundi Avatar answered Nov 15 '22 18:11

Mundi