sbooth
sbooth

Reputation: 16976

Core Data pre-fetching and KVO compliance

I'm having a problem with KVO exceptions being thrown if I attempt to prefetch related entities in Core Data. It doesn't make any sense to me and I can't seem to duplicate the behavior in a simplified project. I'm targeting 10.8 and using ARC.

My data models a music library and I have three entities of interest: Track, Artist, and Album. A track has a single artist and a single album, while an album has a single artist. My Album and Artist objects are uniqued by name (only one artist with a given name in the store).

I want to display a list of tracks with the following fields: title, artist.name, album.title. This is easily accomplished using an NSArrayController for the Track entity and bindings in an NSTableView.

However, since I know that I will be using the artist and album relationships I would like to prefetch them when the tracks are loaded. I created an NSArrayController subclass with a custom performFetch: method that looks like this:

- (BOOL) fetchWithRequest:(NSFetchRequest *)fetchRequest merge:(BOOL)merge error:(NSError *__autoreleasing *)error
{
    NSEntityDescription *entityDescription = [Track entityInManagedObjectContext:[self managedObjectContext]];
    fetchRequest = [[NSFetchRequest alloc] init];
    [fetchRequest setEntity:entityDescription];
    [fetchRequest setRelationshipKeyPathsForPrefetching:@[@"album", @"artist"]];

    return [super fetchWithRequest:fetchRequest merge:merge error:error];
}

I set the NSArrayController's class in IB to my subclass and I expected that the UI lag would disappear (faults were firing with each scroll). However, an exception is thrown:

2013-01-01 10:48:06.965 XXX[10593:303] Cannot update for observer for the key path "artist.name" from , most likely because the value for the key "artist" has changed without an appropriate KVO notification being sent. Check the KVO-compliance of the Track class.

Before the custom NSArrayController subclass everything worked correctly. By simply commenting out the setRelationshipKeyPathsForPrefetching line in my NSArrayController subclass everything works again. I can't determine what the link is between prefetching and KVO. Have any Core Data experts seen something like this before?

Upvotes: 3

Views: 474

Answers (1)

Sam Hatchett
Sam Hatchett

Reputation: 513

I ran into essentially the same problem, and this is what I learned while resolving it:

It seems that NSArrayController does not play nicely with setRelationshipKeyPathsForPrefetching: in all situations. I was executing a prefetching request in a child context and this ended up breaking KVC for an NSArrayController in the parent context, which I imagine to be unintended behavior. In any case, the way I fixed it was to use the "old" style (10.4) prefetching, which would look something like...

NSFetchRequest *trackReq = [[NSFetchRequest alloc] initWithEntityName:@"Track"];
NSArray *fetchedTracks = [context executeFetchRequest:trackReq error:&error];

NSFetchRequest *batchFaultReq = [[NSFetchRequest alloc] initWithEntityName:@"Artist"];
[batchFaultReq setPredicate:[NSPredicate predicateWithFormat:@"SELF.tracks in %@", fetchedTracks];
NSArray *faultedItems = [context executeFetchRequest:batchFaultReq error:&error];

// do something with fetchedTracks

... how you plug this into your NSArrayController subclass is another matter - it's not immediately clear to me how to use this method within a fetchWithRequest: override since you need the fetched results before you can execute the batch faulting request. On a side note, you've experimented with the lazy fetching flag?

Upvotes: 1

Related Questions