Testing Core Data migration with Xcode 6's new Devices screen

Thu Sep 25 2014

Yesterday, I had the task of testing and fixing a Core Data migration issue. Those who have done this will know it's usually a loathsome burden. Fortunately, with the release of Xcode 6, Apple has made this process a little easier with a streamlined app management interface.

Devices Screen Overview

Console

From the Window menu in Xcode, select Devices. In its default state, it's a little confusing, so go ahead and click the expand arrow () at the bottom left of the right panel. The familiar device console from Xcode 5 now hides here.

Add, Remove

As with Xcode 5, we can see a list of apps installed on the device (N.B. App Store apps do not show up in this list), but a new addition is this array of controls at the bottom left of the apps list. The + button adds an app to the device from a .ipa file. This could be exported from an Xcode archive or even downloaded from the App Store. If an app with the same bundle identifier already exists on the device, it will be "modified". As far as I can tell, this is the same process that goes on when an app is updated by the App Store (sans the update animation). The - button removes an app from the device (of course, if it is an App Store app, it won't be listed, so it is limited to removing Ad Hoc or development apps).

Containers

The other new function here is the settings cog, which is for container manipulation. From this menu you can show, download and even replace an app's sandboxed data area (basically the entire sandboxed file system that is available to app on device). This could be useful if you need to reproduce a specific document-related bug or put the app into a specific state repeatedly (like a virtual image). For Core Data purposes, it allows you to easily save or replace the sqlite store.

Core Data Migration Test Workflow

The devices screen allows us to very quickly test our end user's upgrade experience (minus the download delays, but that's another story). In this simple workflow, we'll take an app from production and see what happens when it is upgraded to the latest version (that we have not yet released).

Step 1: Download the live app

Using iTunes, find the app on the App Store (alternately, DuckDuckGo/Google it and the App Store link will be redirected to iTunes and open there - sometimes, this is easier than navigating iTunes itself). Download the app into iTunes. There's no need to install it on the device at this stage. Go to apps view. Right click on the app and select Reveal in Finder. Copy the .ipa file to your desktop and rename it with something like "AppStore.ipa".

Step 2: Archive and export the updated version of the app

In Xcode, archive your current build. Once this is completed, select the archive in the Organizer screen (Archives tab) and click the Export... button. We're after the second option here: Adhoc or Enterprise deployment. It's assumed you have all the valid provisioning profiles set up already. Choose an Adhoc or Enterprise provisioning profile to sign the export with. Export to your desktop with a name like "Update.ipa".

Step 3: Installation and updating

In Xcode, return to the Devices screen. Click the + button under the apps list and select your App Store .ipa file from Step 1 in the file picker.

You should see in the console below a message like this:

Sep 24 16:47:36 iPhone4S-80 SpringBoard[43] <Warning>: Installed apps did change.
    Added: {(
        "com.mycoolcompany.myawesomeapp"
    )}
    Removed: {(
    )}
    Modified: {(
    )}

On the device, launch the app and do whatever you need in order to create the Core Data store. Usually, this just requires launching your app.

On the Devices screen, click the + button again. This time select the updated .ipa file from Step 2.

You should see from the console that the app has now been updated:

Sep 24 16:49:15 iPhone4S-80 SpringBoard[43] <Warning>: Installed apps did change.
    Added: {(
    )}
    Removed: {(
    )}
    Modified: {(
        "com.mycoolcompany.myawesomeapp"
    )}

Step 4: Results and troubleshooting

If your app was open at the time, it will have been terminated (whether it was or not is mostly irrelevant). Launch your now updated app. If it functions as expected without crashing, then congratulations - you've got a great update experience ready for deployment! If the app crashes, the first place you'll want to look is in the console.

If you get a message like this:

Sep 24 16:44:03 iPhone4S-80 MyAwesomeApp[306] <Warning>: CoreData: error: -addPersistentStoreWithType:SQLite configuration:(null) URL:file:///var/mobile/Containers/Data/Application/23DFC42C-1448-40B2-8621-16D7EA0F9961/Library/Application%20Support/MyAwesomeApp/MyAwesomeApp options:(null) ... returned error Error Domain=NSCocoaErrorDomain Code=134100 "The operation couldn’t be completed. (Cocoa error 134100.)" UserInfo=0x155cb730 {metadata={
        NSPersistenceFrameworkVersion = 519;
        NSStoreModelVersionHashes =     {
        ...
        };
        NSStoreModelVersionHashesVersion = 3;
        NSStoreModelVersionIdentifiers =     (
            ""
        );
        NSStoreType = SQLite;
        NSStoreUUID = "D57D0313-C588-4BAB-802C-9783A803D617";
        "_NSAutoVacuumLevel" = 2;
    }, reason=The model used to open the store is incompatible with the one used to create the store} with userInfo dictionary {
        metadata =     {
            NSPersistenceFrameworkVersion = 519;
            NSStoreModelVersionHashes =         {
                ...
            };
            NSStoreModelVersionHashesVersion = 3;
            NSStoreModelVersionIdentifiers =         (
                ""
            );
            NSStoreType = SQLite;
            NSStoreUUID = "D57D0313-C588-4BAB-802C-9783A803D617";
            "_NSAutoVacuumLevel" = 2;
        };
        reason = "The model used to open the store is incompatible with the one used to create the store";
    }

... then you need to use lightweight or manual migration to transition the app from the store version to the current version. For more information on how to do this, see the Resources section below.

If you get a message like this:

Sep 24 16:47:50 iPhone4S-80 MyAwesomeApp[320] <Warning>: CoreData: error: -addPersistentStoreWithType:SQLite configuration:(null) URL:file:///var/mobile/Containers/Data/Application/E68396F6-73AC-4F3A-BFC3-C86A1BF3A638/Library/Application%20Support/MyAwesomeApp/MyAwesomeApp options:{
        NSInferMappingModelAutomaticallyOption = 1;
        NSMigratePersistentStoresAutomaticallyOption = 1;
        NSSQLitePragmasOption =     {
            "journal_mode" = WAL;
        };
    } ... returned error Error Domain=NSCocoaErrorDomain Code=134130 "The operation couldn’t be completed. (Cocoa error 134130.)" UserInfo=0x15625de0 {URL=file:///var/mobile/Containers/Data/Application/E68396F6-73AC-4F3A-BFC3-C86A1BF3A638/Library/Application%20Support/MyAwesomeApp/MyAwesomeApp, metadata={
        NSPersistenceFrameworkVersion = 519;
        NSStoreModelVersionHashes =     {
            ...
        };
        NSStoreModelVersionHashesVersion = 3;
        NSStoreModelVersionIdentifiers =     (
            ""
        );
        NSStoreType = SQLite;
        NSStoreUUID = "92AA336E-14C4-42E3-81AF-3B827B6C3867";
        "_NSAutoVacuumLevel" = 2;
    }, reason=Can't find model for source store} with userInfo dictionary {
        URL = "file:///var/mobile/Containers/Data/Application/E68396F6-73AC-4F3A-BFC3-C86A1BF3A638/Library/Application%20Support/MyAwesomeApp/MyAwesomeApp";
        metadata =     {
            NSPersistenceFrameworkVersion = 519;
            NSStoreModelVersionHashes =         {
               ...
            };
            NSStoreModelVersionHashesVersion = 3;
            NSStoreModelVersionIdentifiers =         (
                ""
            );
            NSStoreType = SQLite;
            NSStoreUUID = "92AA336E-14C4-42E3-81AF-3B827B6C3867";
            "_NSAutoVacuumLevel" = 2;
        };
        reason = "Can't find model for source store";
    }
Sep 24 16:47:50 iPhone4S-80 MyAwesomeApp[320] <Warning>: CoreData: annotation: NSPersistentStoreCoordinator's current model hashes are {
        ...
    }

... then lightweight or manual migration has failed and you'll need to revise your migration strategy and possibly your mapping model file (or consider using one. You can copy and paste the model hashes shown in the log into a diff tool to see which Core Data entities have changed between the two versions. For more help, there are some useful links below.

Resources

Return to list