iCloud, Core Data, migration and model mapping - ios

As said in the Apple documentation
Schema migration using mapping models is not supported (lightweight
migration is supported).
I was wondering about options we have in case we need to deal with iCloud fonctionnality and mapping model migration... I know that I will need to change my Core Data model in the future to add functionalities to my application (and not only in a lightweight way). The thing is that I can't say now which new entities will be needed and which relationships with previous model entities will be set.
I was thinking about a sequence like that:
1 - Launching my app doing the mapping migration of my Core Data Model
2 - Letting it synchronized with iCloud
This will work if iCloud contains transaction log files adapted to the new Model. In case of old transaction log files (means adapted to the old Model) it will failed.
To avoid that I was thinking about doing:
1 - Launching my app letting it synchronized with iCloud
2 - Doing the mapping migration of my Core Data Model
3 - Removing old iCloud data updating it with the new one
This will not work if iCloud already contains updated transaction log files (means adapted to the new Model).
What I need is a way to check if transaction logs in iCloud are compatible with my current Core Data Model. Is there a way to do this ?
Thanks.

I would not recommend using Core Data with iCloud in a production app.
Its not stable enough. There is no simple way of checking if the app is en/disabled in the iCloud settings. And not to mention the problems that can arise if the user turns the iCloud support off after having it enabled.
Migrations are another issue. Lightweight migration works fairly well. But in order to make a mapping model migration work, you need to clean out/evict content from the existing iCloud "ubiquity folder", create a new one (with a new name) and change the value associated with NSPersistentStoreUbiquitousContentURLKey. And, you need to make sure that all clients do the same. Its complex but doable. Not sure if its worth it though...
Regarding the version compatibility issue, I've not yet tried it, but an idea would be to somehow get the store metadata from an iCloud transaction log and through the "compatibleWithStoreMetadata" method on ManagedObjectModel check if the version matches:
// Get current model
NSManagedObjectModel *myModel = [self managedObjectModel];
// Check compatibility
BOOL isCompatible = [myModel isConfiguration:nil compatibleWithStoreMetadata: metadataFromTransactionLogEntry];

Related

Ensembles and Core Data Light Migration

I am currently doing some tests with Ensembles, specifically testing Core Data light migration.
My current configuration is as follow:
Device-A running my app with data model 1
Device-B running my app with data model 2
data model 2 is based on data model 1 with one additional string property, which is optional
My scenario is as follow:
At the beginning, running my app with data model 1 on both Device-A, and Device-B, everything synced fine using Ensembles (iCloud configuration)
On Device-B, install and run my updated app using data model 2
On Device-A, keep running my old app using data model 1, and add a new record
The result: the new record added on Device-A is uploaded to iCloud and then synced to device-B
My question: can I configure Ensembles to prevent it from uploading changes to iCloud in case that related data model is not the latest one? (i.e. in my case, Device-A uploads an object based on data model 1 while iCloud is already based on data model 2)
Thanks in advance!
UPDATE 1:
Drew, thank you very much for your answer. I definitely agree that uploads can't (and probably shouldn't) be prevented as Ensembles is a decentralised, peer-to-peer system.
In the ideal case, I would like that the device with the new data model will ignore data that is based on the old data model. (in a similar way to the existing behavior where the device with the old data model will ignore any data based on the new data model). Is that supported?
If not, please consider the following scenario as an example:
The old data model has an entity called 'Book' with two properties: title, and author (both fields are non optional)
The new data model has a new optional property called titleFirstLetter that should hold the first letter of the title field.
Currently, when Ensembles is not involved, I have full control when saving new NSManagedObject to the persistence store. Therefore, the updated code of my app which responsible for adding a new book, will make sure to extract the first letter from the title field and save it to the new titleFirstLetter property. (i.e. a book titled Catch-22 will have C in the titleFirstLetter property when book is saved).
In addition, when light migration occurs on the core data stack, I detect that, and perform a one-time procedure where I iterate all existing books in the database, and set the titleFirstLetter according to the title value. From this point and on, the database is consistent and valid, while the new code will ensure that future books added to the database will keep database valid.
Regarding Ensembles, if I don't have any control on old data coming from devices with older data model, how can I fill the new property of titleFirstLetter, if my code is never being called?
Thank you for your kind assistance!
You can't prevent it, no. Ensembles is a decentralised, peer-to-peer system. There is really no way for one device to know the current state of another device, so you couldn't prevent an upload.
The updated device should be capable of handling the old data from the other device. The device with the old model will ignore any data based on the new model, until it too is updated. Then it will merge all of that ignored data.
It is best to avoid migrations where possible, and stick to simple stuff like adding properties or entities, rather than tricky refactors. If you need to make a lot of changes, consider simply starting with a new ensemble (e.g. change the ensembles identifier).

iCloud and lightweight migration

I've a published app synchronizing a Core Data with iCloud.
I need to update the model adding two attributes and then populate these new fields.
I've tested lightweight migration locally and works fine, I can see the old data migrated into the new model scheme.
When I activate iCloud, old data saved in the ubiquity container doesn't sync with the new model schema.
What is the expected behavior?
Should I be able to sync data both on old and new model versions?
How can I achieve it and test this situation?
I've read:
CoreData versioning
and
Understanding Core Data iCloud Store Migration When Testing an iOS App Update
but, actually, I'm very confused.
What's supposed to happen is that iCloud data only syncs between devices that are using the same version of the data model. If you upgrade the app on one device but not on a second device, they won't sync changes until the second device also updates to the new model version.
If that's not what you're seeing, please add more details about the problem you're seeing.

Will adding a new fetch request template to Core Data break previous versions of model?

I want to add a new fetch request template to a core data model. I know I could do it programmatically, but all the other fetch request templates are present in the core data editor and it makes sense to add the new one alongside them.
My question is, since this is part of a point release for an app that has been in the store for quite some time already, is there any chance this will break existing installs? Does it count as some kind of migration, or not? Obviously we will QA it, but I'd like to know the answer in advance, and googling has been fruitless so far.
It's not stated explicitly anywhere, but in the NSManagedObjectModel documentation, it says the following:
Changing Models
Since a model describes the structure of the data in a persistent store, changing any parts of a model that alters the schema renders it incompatible with (and so unable to open) the stores it previously created. If you change your schema, you therefore need to migrate the data in existing stores to new version (see Core Data Model Versioning and Data Migration Programming Guide). For example, if you add a new entity or a new attribute to an existing entity, you will not be able to open old stores; if you add a validation constraint or set a new default value for an attribute, you will be able to open old stores.
It doesn't explicitly mention fetch requests, but these don't have anything to do with the schema, so I think you'll be fine.

iOS: DDL Commands during runtime

i want to add tables/columns to a database during runtime.
Currently I'm using Core Data.
I know that there's a possibility to do so in XCode (add new data model version), but I definitely can't use that way, because I receive the database schema from a web service.
Is there any good possibility to run ddl commands during runtime when using Core Data, or is it just possible with directly using sqlite (or a wrapper/ormapper)?
If it's better to use a wrapper/ormapper please give me some suggestions about which should be used in this case.
Workflow should be:
start app
check if database is up to date
if new version of schema is available from a web service do DDL commands
continue with app workflow
PS: Please no answers which describe alternatives modifying the schema with XCode!
Can you modify the Core Data model at run time? Yes...but, it probably won't work the way you want it to work.
Core Data's API makes it possible to construct or modify every detail of a data model at run time. Xcode's model editor is a convenience, but you could skip it and do everything in code if you wanted. For example, NSEntityDescription's properties attribute (which covers both attributes and relationships) is writeable. You could create a new NSAttributeDescription and update the entity's properties to contain it. Bang, you just added a new attribute to the entity. Similarly, NSManagedObjectModel's entities property is writeable, so you could create a new NSEntityDescription and add it to the model. That gives you a new entity, created at run time.
But, and it's a big one: you can only do this before you load the data store. Once you load your persistent store, altering the model will throw an exception. When Core Data loads a persistent store, it compares the model file to the model used in the store file. They must match, and you can't do anything to change this fact after loading the store. Once you load the store, the model is fixed.
What's more, even if you modify your model before loading the persistent store, you can only load persistent stores that match the current version of the model-- unless, that is, you also write code to migrate the persistent store to the new model. How hard that is depends on the nature of the changes. At a minimum then, you would need to make any changes before loading previously saved data, and then also arrange to do model migration to update the persistent store to use the new model.
With Core Data the model (schema) and data are stored separately and matched up when the store is loaded. That's not how SQLite works internally but it's the approach that Core Data enforces.

How to upgrade iOS Core Data model and data

I've got an iOS app which uses Core Data (SQLite on the backend). It only has one entity, 'Item'. There is a SQLite file bundled with the app, with hundreds of items pre-added, so when the user downloads the app from the App Store it already has the data.
The only entity has a BOOL favorite attribute which the user can alter, used -of course- to check if an item is among the user favorite items.
I'm planning to publish an update of the app with more items pre-built in the app bundle (a new SQLite file), but I want to keep the user favorites. As well, in this version my Core Data model will suffer a few modifications (I need some new properties in the 'Item' entity). The new set of items is a superset of the old items (an item in the old version of the app shall be in the new version, always).
I've been struggling with this a lot and I can't find a solution to this. I'm able to upgrade the data model introducing new properties into my entity while keeping the user favorites (performing a so-called lightweight migration, but then I'm not able to merge old and new items. On the other hand, I'm able to get the new pre-added items, but then the favorite-related data is discarded.
Any hint? Thank you all in advance
I finally managed to solve the problem.
I've got two NSPersistentStoreCoordinators, two NSManagedObjectContexts and two NSManagedObjectModels in my app delegate: one set to use in the application (the updated one) and another set pointing to the old store. In my app delegate didFinishLaunchingWithOptions: method I load all the user's favorites from the old store and save them into the new one. That's the only point in the app where I touch the old store.
Thank you all anyway!
I would suggest creating a second database with your new stuff in there, but without favorites. Then you pull favorites from your old database and insert them into your new one. Remove the old database and replace with the new one. That seems like the most straight-forward solution. There may be functionality built into Core Data for these situations, but chances are this is easier.

Resources