Search code examples
iosiphonecore-dataicloud

How to seed initial data to Core Data + iCloud?


I'm working on a new app that uses Core Data and iCloud. I'm following the iCloudCoreDataStack demo and the iCloud Design Guide. So far the synchronization between devices is working well, but I haven't figured out how to seed a small amount of data the first time the app is used on the user's first device and skip the seeding if the app is used on a second device (since it should download from iCloud instead).

This should be easy, just ask the iCloud Container if it has any data. Download the data if it exists or create new data if it doesn't. But I couldn't find a way to do this :-(

I can think of three ways to solve this:

  1. Use migratePersistentStore:toURL:options:withType:error: I have a very small amount of data, so for this case this feels like overkill

  2. Store a value on NSUbiquitousKeyValueStore to mark if the initial synchronization has been made I tried using NSUbiquitousKeyValueStore, but sometimes it would take too long to get the value from the UbiquitousKeyValueStore, so the seed data would be created even when not needed, resulting in duplicates.

  3. Use a sentinel file to have the same effect of #2 (I'm not sure how to implement this)

The App is iOS 7 only and new, so there's no need to migrate old user data.

Every relevant tutorial and book that I found seemed to be using the pre-iOS7 super complex way of doing things (e.g. using a fallback store) that is not necessary on iOS 7.

Either I'm missing something (often the case) or this is more complicated than it should be. I appreciate any suggestions and pointers.


Solution

  • It is never a good idea to seed a distributed datastore with an initial dataset. Generally this initial data can be packaged into a store file that is shipped with the application, and added as a second persistent store to the coordinator used by your application's managed object context.

    That said, it is possible, although unwise to seed based on the completion of Core Data's initial import.

    You need to wait for NSPersistentStoreCoordinatorStoresDidChangeNotification with NSPersistentStoreUbiquitousTransitionTypeKey set to NSPersistentStoreUbiquitousTransitionTypeInitialImportCompleted.

    If there is no data in the store you can seed your initial data set at that point.

    However it is important to realize that multiple devices could receive the initial import notification without importing the seeded data and thus seed it again. There is no way to avoid this.


    On the point of shipping a second persistent store with your application, to serve as seed data.

    This is accomplished as Marcus points out below by adding it as a read only store to the persistent store coordinator that is in use by your app's managed object context.

    NSDictionary *options = @{ NSReadOnlyPersistentStoreOption: @YES };
    [_psc addPersistentStoreWithType:NSSQLiteStoreType
                       configuration:nil
                                 URL:seedStoreURL
                             options:options
                               error:&localError];
    
    NSDictionary *iCloudOptions = @{ NSPersistentStoreUbiquitousContentNameKey: @"storeName" };
    [_psc addPersistentStoreWithType:NSSQLiteStoreType
                       configuration:nil
                                 URL:iCloudStoreURL
                             options:iCloudOptions
                               error:&localError];
    
    _moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    [appMOC setPersistentStoreCoordinator:_psc];
    

    This way your application's managed object context has access to data from both stores and will fetch both sets from fetch requests. The managed object context is also smart enough to automatically insert new objects into the correct store (because one of them is read only).

    The only trick is that if you want your user to be able to modify data from the seed store you'll need to move those objects to the iCloud store.

    This is a much easier approach than trying to migrate the dataset wholesale because ideally your user will only be using a single device at a time. In the case of a collision you'll at most have to resolve a few duplicate records as opposed to trying to detect duplication across the entire dataset.