profile for monkeydom at Stack Overflow, Q&A for professional and enthusiast programmers

cocoa-dom

coding bits I use, come across, like, hate, the whole shebang.

twitter | github | mastodon     rant-dom     rss | archive

Permalink
Home

0x8badf00d, Core Data and Migration

Let's begin with the basics. In iOS there is a watchdog process - a watchdog that checks if an App takes more than about 20 seconds to start without getting back to the OS it will be killed. In the crash reports this killing is indicated by an exception code of 0x8badf00d - Ate Bad Food. Getting back to the OS more or less means that your delegate call of application:didFinishLaunchingWithOptions: needs to return control to the runloop in time. If you don't: you are killed. The user experience of this is that your Default.png is shown for about 20 seconds, and then the iPhone/iPad returns to the home screen.

Fun Fact: On iOS Devices that support multitasking the watchdog can be confused by pressing home and go back to the app again in intervals slightly less than 20 seconds. So if an App is showing this behavior, you might get it to work again using this stunt.

This is all good and well, the user should not have to wait for more than 20 seconds for an App to actually do something that the user can see or interact with. This is where Core Data comes in.

Core Data is a great framework that provides developers an abstraction to a local database. You do so by specifying a data model in Xcode, and provide classes for your database objects. In code you usually initialize your Core Data infrastructure very early on, because you rely on the data in the database for almost everything your app does. Usually that step takes little time - if the app is new you maybe want to copy an existing database as a first step, otherwise it just opens up the database files and returns.

However, if you want to store additional information in your database, you need to update your model. And if you do so, you need to migrate your data once to accommodate the new model. This happens in that early on initialization step. As migration goes there are two kinds of migration: Lightweight migration and manual migration. Lightweight migration is fast and can be done by just issuing SQL statements to the underlying database. Manual migration causes every object to be loaded and be updated. If you can achieve your changes with lightweight migration, especially on iOS devices you should do so. However, sometimes it is necessary to do some manual work in the migration process.

  • This is where the shit can hit the fan: Consider you are initializing Core Data before the end of  application:didFinishLaunchingWithOptions: because you want to use some of the existing data to prepare badges and UI state in the first viewWillAppear: methods of your top view controllers. Consider further that you are making a major update to your App and the database needs a manual migration. Now what easily can happen is that the watchdog literally bites your app in the ass.
  • And even better, during development you probably won't notice. Why? Because of many reasons, I try to list many of them:
  • The watchdog is inactive if you are running in the debugger. Your App Start may take a while, but your app will always start up.
  • The migration step only happens once, unless your specifically testing for it over and over again. And it is most likely that that step is done while you are running in the debugger to check if everything goes right.
  • The data you need to test needs to be big. In most test and development scenarios you have realistic data, with a wide variety of items being in there to catch most cases, but you won't do very big data because that slows down overall roundtrip time.
  • Your test devices most probably will be the fastest you have most of the time, again for roundtrip reasons. And faster devices migrate faster. Probably fast enough to not show the problem.

When you look at the customers that will have that problem out there you see another problem: They are probably your best customers. If they store a lot of data in your app, they use it a lot. If there is a lot of data, migration takes long. Tada! Horrible.

So now that we have established that this problem is a real turdball coming your way, how do you prevent/fix it?

Solution #1: do lightweight migration. If you can do that. This will be fast enough to not call the wrath of the Watchdog.

Solution #2: if you can't do lightweight migration, make sure your migration step won't be caught by the watchdog. E.g. your initialization of Core Data needs to be done after the app has returned from the application:didFinishLaunchingWithOptions: callback. A really good way of testing this is adding a simple sleep(25) in the code that actually creates your persistentStoreCoordinator. That way without having all test data, migration always takes long enough to trigger the watchdog. And again be sure to not attach the debugger, or the watchdog won't bite.

Lessons learned:

  • Beware of the watchdog.
  • Core Data is great, but migration can really hit you quite unexpectedly.
  • If you use Core Data - put in that sleep(25) call now and test if you get bitten. If so fix it so it won't bite you unprepared in the future when you update your model.
  • Prepare feedback for user migration. As it turns out users don't make a difference between an app that appears frozen for a long period of time and an app that actually crashes. Even if migration will only happen once for each major model update, that moment can be crucial.