Search code examples
ioscore-datacore-data-migration

iOS Core Data migration time out


I am getting this error message (and subsequent crash) in Xcode organiser that I suspect is related to a Core Data migration that takes too long to complete:

Oct 4 09:51:14 unknown SpringBoard[30] : appName failed to launch in time

This migration works find in the simulator, even when I insert artificial delays (using sleep(100), for example).

It is only when I try to test on an iPhone 4 or iPod 4th generation that I am getting this timeout error message.

If a migration is not needed, by providing the correct sqlite3 database version during the build, there is no crash.

Here is what is happening, in code:

In the application delegate :

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    

    PropertiesTableViewController *propertiesTableViewController = 
            [[PropertiesTableViewController alloc] initWithManagedObjectContext:self.managedObjectContext];

    UINavigationController *newNavController = 
                        [[UINavigationController alloc] 
                         initWithRootViewController:propertiesTableViewController];


    [propertiesTableViewController release];
    [window addSubview:newNavController.view];


    NSLog(@"UUID: %@", [[UIDevice currentDevice] uniqueIdentifier]);

    [window makeKeyAndVisible];

    return YES;
}


- (NSManagedObjectContext *) managedObjectContext {

    if (managedObjectContext != nil) {
        return managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        managedObjectContext = [[NSManagedObjectContext alloc] init];
        [managedObjectContext setPersistentStoreCoordinator: coordinator];
    }
    return managedObjectContext;
}`

The persistentStoreCoordinator is a fairly long method, happy to provide it if needed. It migrates two databases, around 500kBytes each. The first always succeeds but the second one always fails (on the device), however both always succeed in the simulator. The propertiesTableViewController present a table view with the data it gets from the migrated database, so migration has to happen before propertiesTableViewController comes to view.

I have also tried trying to trigger the migration from inside the propertiesTableViewController (in the viewDidLoad method) using:

  • [NSThread detachNewThreadSelector:@selector(getMOC) toTarget:self withObject:nil] --> crashes as the table view can't access the managed object context which is not ready since migration is still underway.
  • [self performSelectorOnMainThread:@selector(getMOC) withObject:nil waitUntilDone:YES]; --> Crashes with the same timeout error

The getMOC method is this:

-(void)getMOC
{
    NSAutoreleasePool *autoreleasePoolProperty = [[NSAutoreleasePool alloc] init];
    NSLog(@"getting appDelegate from PropertiesTableViewController");

    iNspectorAppDelegate *appDelegate = (iNspectorAppDelegate *)[[UIApplication sharedApplication] delegate];
    NSLog(@"getting managedObjectContext from PropertiesTableViewController");    
    self.managedObjectContext = [appDelegate managedObjectContext];
    NSLog(@"got managedObjectContext from PropertiesTableViewController");   
    [autoreleasePoolProperty release];  
    [self performSelectorOnMainThread:@selector(finishedGettingPropertyData) withObject:nil waitUntilDone:NO];
}

Solution

  • I think you need to defer showing the navigation controller until migration is complete, i.e. you might need to display a "progress controller".

    The easiest way is probably to display the progress controller at startup:

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
    {
      ...
      [window addSubview:progressController.view];
      [window makeKeyAndVisible];
      [self performSelectorInBackground:@selector(loadPersistentStore) withObject:nil];
    }
    
    -(void)loadPersistentStore
    {
      [self persistentStoreCoordinator];
      [self performSelectorOnMainThread:@selector(persistentStoreLoaded) withObject:nil waitUntilDone:NO];`
    }
    
    -(void)persistentStoreLoaded
    {
      [progressController.view removeFromSuperview];
      self.progressController = nil;
    
      // Load the normal VC here
      ...
    }
    

    A slightly better option is to first try loading the persistent stores with NSMigratePersistentStoresAutomaticallyOption turned off. If that fails, then display the progress VC and try again in the background; if it succeeds then you can display the normal VC directly (In the code above, you could just call -persistentStoreLoaded directly from -application:didFinishLaunchingWithOptions:).