How to populate a core data store programmatically?
- by jdmuys
I have ran out of hairs to pull with a crash in this routine that populates a core data store from a 9000+ line plist file.
The crash happened at the very end of the routine inside the call to [managedObjectContext save:&error]. While if I save after every object insertion, the crash doesn't happen. Of course, saving after every object insertion totally kills the performance (from less than a second to many minutes).
I modified my code so that it saves every K insertions, and the crash happens as soon as K = 2.
The crash is an out-of-bound exception for an NSArray:
Serious application error. Exception was caught during Core Data change processing: *** -[NSCFArray objectAtIndex:]: index (1) beyond bounds (1) with userInfo (null)
Also maybe relevant, when the exception happen, my fetch result controller controllerDidChangeContent: delegate routine is in the call stack. It simply calls my table view endUpdate routine.
I am now running out of ideas. How am I supposed to populate a core data store with a table view?
Here is the call stack:
#0 0x901ca4e6 in objc_exception_throw
#1 0x01d86c3b in +[NSException raise:format:arguments:]
#2 0x01d86b9a in +[NSException raise:format:]
#3 0x00072cb9 in _NSArrayRaiseBoundException
#4 0x00010217 in -[NSCFArray objectAtIndex:]
#5 0x002eaaa7 in -[UITableView(_UITableViewPrivate) _endCellAnimationsWithContext:]
#6 0x002def02 in -[UITableView endUpdates]
#7 0x00004863 in -[AirportViewController controllerDidChangeContent:] at AirportViewController.m:463
#8 0x01c43be1 in -[NSFetchedResultsController(PrivateMethods) _managedObjectContextDidChange:]
#9 0x0001462a in _nsnote_callback
#10 0x01d31005 in _CFXNotificationPostNotification
#11 0x00011ee0 in -[NSNotificationCenter postNotificationName:object:userInfo:]
#12 0x01ba417d in -[NSManagedObjectContext(_NSInternalNotificationHandling) _postObjectsDidChangeNotificationWithUserInfo:]
#13 0x01c03763 in -[NSManagedObjectContext(_NSInternalChangeProcessing) _createAndPostChangeNotification:withDeletions:withUpdates:withRefreshes:]
#14 0x01b885ea in -[NSManagedObjectContext(_NSInternalChangeProcessing) _processRecentChanges:]
#15 0x01bbe728 in -[NSManagedObjectContext save:]
#16 0x000039ea in -[AirportViewController populateAirports] at AirportViewController.m:112
Here is the code to the routine. I apologize because a number of lines are probably irrelevant, but I'd rather err on that side. The crash happens the very first time it calls [managedObjectContext save:&error]:
- (void) populateAirports
{
NSBundle *meBundle = [NSBundle mainBundle];
NSString *dbPath = [meBundle pathForResource:@"DuckAirportsBin" ofType:@"plist"];
NSArray *initialAirports = [[NSArray alloc] initWithContentsOfFile:dbPath];
//*********************************************************************************
// get existing countries
NSMutableDictionary *countries = [[NSMutableDictionary alloc] initWithCapacity:200];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Country" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *values = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (!values) {
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
int numCountries = [values count];
NSLog(@"We have %d countries in store", numCountries);
for (Country *aCountry in values) {
[countries setObject:aCountry forKey:aCountry.code];
}
[fetchRequest release];
//*********************************************************************************
// read airports
int numAirports = 0;
int numUnsavedAirports = 0;
#define MAX_UNSAVED_AIRPORTS_BEFORE_SAVE 2
numCountries = 0;
for (NSDictionary *anAirport in initialAirports) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSString *countryCode = [anAirport objectForKey:@"country"];
Country *thatCountry = [countries objectForKey:countryCode];
if (!thatCountry) {
thatCountry = [NSEntityDescription insertNewObjectForEntityForName:@"Country" inManagedObjectContext:managedObjectContext];
thatCountry.code = countryCode;
thatCountry.name = [anAirport objectForKey:@"country_name"];
thatCountry.population = 0;
[countries setObject:thatCountry forKey:countryCode];
numCountries++;
NSLog(@"Found %dth country %@=%@", numCountries, countryCode, thatCountry.name);
}
// now that we have the country, we create the airport
Airport *newAirport = [NSEntityDescription insertNewObjectForEntityForName:@"Airport" inManagedObjectContext:managedObjectContext];
newAirport.city = [anAirport objectForKey:@"city"];
newAirport.code = [anAirport objectForKey:@"code"];
newAirport.name = [anAirport objectForKey:@"name"];
newAirport.country_name = [anAirport objectForKey:@"country_name"];
newAirport.latitude = [NSNumber numberWithDouble:[[anAirport objectForKey:@"latitude"] doubleValue]];
newAirport.longitude = [NSNumber numberWithDouble:[[anAirport objectForKey:@"longitude"] doubleValue]];
newAirport.altitude = [NSNumber numberWithDouble:[[anAirport objectForKey:@"altitude"] doubleValue]];
newAirport.country = thatCountry;
// [thatCountry addAirportsObject:newAirport];
numAirports++; numUnsavedAirports++;
if (numUnsavedAirports >= MAX_UNSAVED_AIRPORTS_BEFORE_SAVE) {
if (![managedObjectContext save:&error]) {
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
numUnsavedAirports = 0;
}
[pool release];
}