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:
Use migratePersistentStore:toURL:options:withType:error: I have a very small amount of data, so for this case this feels like overkill
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.
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.
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.
Try this approach:
Before creating the local store check if one has already been created in iCloud by looking for the presence of a directory with the 'NSPersistentStoreUbiquitousNameKey' name in the iCloud /CoreData directory.
If one exists then this means that some other device has already created a store and shared it in iCloud so when creating your local store on the device don't add seed data because this will already exist in iCloud.
If one does not exist then no other device has shared the store in iCloud yet so you can probably create your seed data and sync to iCloud.
NOTE that the following scenarios are not catered for:
A user is running the app on a device that does not have iCloud enabled - if the user chooses to now sync with iCloud from this device you will have to deal with issues arising from trying to merge data from this device with data already in iCloud. Once again you can check for the presence of data in iCloud and then ask the user whether they want to try merging data from the device or whether they want to replace the data with data from iCloud.
A user is running the app on a device that is not connected to the network - and has not had any data synced from iCloud yet so thinks there is no file already in iCloud. The app will then create seed data and when it gets a network connection Core Data will merge the data - you app may have to deal with the ensuing problems.
For these scenario's you may need to try user education.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With