views:

256

answers:

3

I have a Core Data application which I plan to update with a new schema. The lightweight migration seems to work, but it takes time proportional to the amount of data in the database. This occurs in the didFinishLaunchingWithOptions phase of the app.

I want to avoid <app> failed to launch in time problems, so I assume I cannot keep the migration in the didFinishLaunchingWithOptions method.

I assume the best method would involve performing the migration in a background thread. I assume also that I'd need to defer loading of the main ViewController until the loading completes to avoid using the managedObjectContext until initialization completes.

Does this make sense, and is there example code (maybe in Apple sample projects) of this sort of initialization?

A: 

You might put your Core Data updates into an NSOperation, which can be added to an operations queue in didFinishLaunching... and which can operate in the background, by overriding the operation's -main method.

Take a look at this tutorial page to get a general idea of what's involved. Use KVO with the operation's isFinished property to update the state of your application -- you might use this key's value to warn the user that the migration is still taking place, for example, before presenting any data.

Alex Reynolds
http://stackoverflow.com/questions/2840406/nsoperations-or-nsthread-for-bursts-of-smaller-tasks-that-continuously-cancel-eac maybe helpful in some way.
RickiG
You can't put the migration in a `NSOperation` because A) it needs to be the main context and a `NSOperation` would put it in the background; and B) any code that touches the `NSManagedObjectContext` on the main thread would trigger a *second* migration while the background thread is running.
Marcus S. Zarra
I do background Core Data work with `NSOperation` instances, so I think I have to respectfully disagree. I read through the migration guide again and there is nothing in there about this needing to be done on the main thread.
Alex Reynolds
Any advantage in using NSOperation as opposed to blocking the main run loop, if your app can't do anything without Core Data instantiated anyway?
sehugg
Your UI can operate unimpeded on the main thread. So your app can provide UI cues to the user that a migration is still in progress, for example.
Alex Reynolds
+1  A: 

You can't put migration in a NSOperation because it will need to run on the main thread. What you need to do is to get out of the -applicationDidFinishLaunching: method without touching the Core Data stack. If you can finish that method (and that run loop cycle) quickly then your app will not be terminated and you can take as long as the user will put up with to finish your migration.

See my answer here: http://stackoverflow.com/questions/2654426/how-to-switch-from-core-data-automatic-lightweight-migration-to-manual/2654651#2654651

Update

To clarify my position on this. It is inherently possibly to do just about anything. However, doing a migration on a background thread is a bad idea. It is extremely difficult to guarantee that the stack will never get touched during the migration as well as a whole host of other threading specific complications.

It is possible to do it but it involves a high degree of risk that is completely unnecessary. The main thread can and should be used to do a migration of the main Core Data stack. It is easy to put up a modal dialog to let the user know a migration is occurring and then perform the migration on the main thread.

If you are in a situation where your migrations are taking a signficant amount of time then it is also highly recommended that you switch from automatic migration to manual migration with a mapping model so that you can:

  • Easily back out of the migration if needed.
  • Perform the migration in chunks in case the user quits your application.
  • Give the user solid feedback on how far along the migration is and when it will be done.
Marcus S. Zarra
+1  A: 

Sorry Marcus, I have to respectfully disagree. You can migrate in the background.

My migriation runs on a background thread. It can take over 10 seconds on slow devices, so I start it on a background thread and have a specific modal view controller to show progress.

The way to do this is split your normal loading sequence into two phases.

Phase 1) Do everything you would normally do at launch that doesn't require managed objects. The end of this phase is defined by a check to determine if migration is required.

Phase 2) Do everything that normally happens at launch that requires managed objects. This phase is an immediate continuation of Phase 1) when no migration was required.

This way, your app finishes launching regardless of the duration of migration processing.

To perform a long migration successfully, I use a modal view controller showing feedback of the migration progress to the user. I then commence the migration in the background thread, while the modal view controller uses KVO to update it's progress bar.

At the end of the migration, it closes down the whole core data "stack", and a callback to the main thread will dismiss the modal and continue on to Phase 2).

This whole process works flawlessly, although I still have an open question on a way to have the automatic lightweight migration reveal it's progress like the manual migration does.

ohhorob
This sounds like what I need, except I would have to defer the loading of `NSMainNibFile` by `UIApplication` since all of my views touch Core Data. I'm sure there's a way to do this manually but I haven't found the right reference.
sehugg
Absolutely. I would remove the `NSMainNibFile` from the info plist, and load it when you know everything is migrated and ready to go. I remember reading this http://www.dragthing.com/blog/?p=246 by James Thomson (developer of PCalc) doing this manually to assist with his perceived launch times.
ohhorob
Thanks for the link .. that's what I figured just scared at getting the exact method right ;) If migration is required, I `loadNibNamed` a loading screen and add it to the window, then `makeKeyAndVisible`. Then I `performSelector` with delay of 0.0 to Part II of the loading process, which tweaks the run loop. Then I load the main view .nib which adds it to the window automatically (i'm not clear on how exactly that works).
sehugg
I would put that at a expert level design. Trying to do that on a background thread is extremely dangerous and very easy to screw up. For those reading this as a solution, I do not recommend it **AT ALL**. You are far better off putting up the exact same modal view and then processing the migration on the main thread. Far fewer chances of screwing up the user's data. Hence my blanket recommendation of doing all migrations on the main thread.
Marcus S. Zarra
So what was once something you can't do is now a recommendation.
Alex Reynolds
Please read what I said. I said that I recommend doing migrations on the main thread only. I prefer to use the word can't as it gets heard a lot better than "you really really really should not do this".
Marcus S. Zarra
I agree with Marcus on this. It's far safer to use manual migration than trying to hack the automatic migration to run on a background thread. I've learned to be very cautious around multithreading and Core Data.
Brad Larson