I have a Core Data model representing a TV guide on iOS 4+, with 3 classes:
Channel
(BBC 1)Program
(Top Gear)Broadcast
(Top Gear on BBC 1 on Monday at 8pm)
I have about 40 channels, 8000 programs and 6000 broadcasts, and I would like to fine-tune the import process so that it doesn't take up to a minute to run.
Importing the channels and programs is easy because these are independent objects. A broadcast however has a relationship to a channel and to a program (1-to-many), and both channels and programs have inverse relationships to the broadcasts (many-to-1). To speed things up I have an in-memory dictionary of fault channels and programs that have only their Web Service identifier prefetched: I create a broadcast and look through both dictionaries to get the corresponding channel and program without a round-trip to the database.
But when I assign a program or a channel to a broadcast, the channel and program's inverse relationships access trigger a fault of both objects right away, causing a massive slowdown (6000 * 2 requests) and consequent memory pressure as shown in the Core Data Faults Instruments report. I tried pre-fetching the broadcasts
relationship on both channels and programs, but the relationship still gets faulted.
Do you have any idea why the inverse relationships get accessed and fault their parents? How do I avoid reading from the database when saving a relationship?
UPDATE: Sample code, my assign / update method for a Broadcast
instance. The dictionary
variable comes from the Web Service and channels
and programs
contain the fault channels and programs objects indexed by Web Service identifier. Faulting occurs on the self.program = program
and self.channel = channel
lines.
- (BOOL)assignWithDictionary:(NSDictionary *)dictionary channels:(NSDictionary *)channels programs:(NSDictionary *)programs {
// Add channel relationship
NSNumber *channelIdentifier = [dictionary objectForKey:@"channel_id"];
if (self.channel == nil || ![self.channel.identifier isEqualToNumber:channelIdentifier]) {
Channel *channel = [channels objectForKey:channelIdentifier];
if (channel == nil) {
NSLog(@"Broadcast %@ has invalid channel: %@", identifier, channelIdentifier);
return NO;
}
self.channel = channel;
}
// Same to add a program relationship
// ...
}
And my fetch request to get the channels or the programs list:
- (NSDictionary *)itemsForEntity:(NSEntityDescription *)entity {
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
NSError *error = nil;
NSArray *itemsArray = nil;
request.entity = entity;
request.relationshipKeyPathsForPrefetching = [NSArray arrayWithObject:@"broadcasts", nil];
request.propertiesToFetch = [NSArray arrayWithObjects:@"identifier", @"version", nil];
itemsArray = [self.context executeFetchRequest:request error:&error];
NSAssert1(error == nil, @"Could not fetch the items from the database: %@", error);
{
NSMutableDictionary *items = [NSMutableDictionary dictionaryWithCapacity:itemsArray.count];
for (NSManagedObject *item in itemsArray) {
[items setObject:item forKey:[item valueForKey:@"identifier"]];
}
return [NSDictionary dictionaryWithDictionary:items];
}
}