Prepopulated sqlite file in the app bundle. What happens after the existing users update? - ios

I have an app on the store which points to the appname.sqlite file in the document directory.Here's the old code:
NSURL *storeURL = [[self applicationDocumentsDirectory]URLByAppendingPathComponent:#"MyApp.sqlite"];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:#{NSMigratePersistentStoresAutomaticallyOption : #YES,NSInferMappingModelAutomaticallyOption : #YES}; error:&error]) {
abort();
}
I now want to give an update. The new feature being a prefilled .sqlite database which is there in the bundle.See the following
NSURL *storeURL = [NSURL fileURLWithPath: [[NSBundle mainBundle] pathForResource:#"MyNewPrefilled" ofType:#"sqlite"]];
It would work ok on the fresh installation the app.But what happens to the users who have logged in and have a lot of data saved in the db (old sqlite in the app directory)? I would lose it because I now am pointing to the .sqlite in the app bundle (MyNewPrefilled.sqlite) and not the app directory one. How do I get the old data back from the old sqlite file of doc directory to my new one in the bundle?
FYI: I use code data migration already (involving version numbers in datamodels) which works ok when i change datamodel in updates.

Apple recommends to use two persistent stores (in the same model) to separate data that is user generated and data that is static. Presumably, if your data is static, you should do exactly that and have a read-only store for the static data. See for example Configurations in the Core Data Programming Guide.
Another approach is
to copy the bundle seed database into the documents directory, and then
read the data from the old store and
copy it to the new one.
Finally, you can delete the old store.

Related

Unable to create a readonly sqlite store in Core Data: Cocoa Error 260

I'm trying to create a NSSQLiteStoreType with the readonly option (NSReadOnlyPersistentStoreOption). This fails if the sqlite file doesn't exist (see code below). If it does exist, the store is added without any errors.
The error I get is Cocoa Error 260:
NSFileReadNoSuchFileError = 260, // Read error (no such file)
So it looks like CoreData tries to read a file that doesn't exist, instead of creating a new one...
It seems that when adding NSReadOnlyPersistentStoreOption you can only open a previously existing store, but not create one. This doesn't make sense to me.
Is there any way to create a brand new readonly store in Core Data?
If not, is there some workaround?
// DB URL
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *dbURL = [[fm URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask] lastObject];
dbURL = [dbURL URLByAppendingPathComponent:#"store.sqlite"];
// Object Model
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"Model" withExtension:#"momd"];
NSAssert([fm fileExistsAtPath:[modelURL path]], #"File not found");
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
// Store Coordinator
NSPersistentStoreCoordinator *coord = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
// Add a readonly SQLite store
NSError *err = nil;
NSPersistentStore *store = [coord addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:dbURL
options:#{NSReadOnlyPersistentStoreOption : #YES}
error:&err];
if (store == nil) {
// I get a Cocoa Error 260.
NSLog(#"Error: %#", err);
}
Creating a new, empty, read-only store makes no sense, and the results you're seeing are exactly what would be expected. By specifying read-only you are specifically indicating that no file should be written, so as a result.... no file is written.
It's hard to tell what you're trying to accomplish. If the file were created, you would not be able to use it, since it would contain no data and since the read-only flag would prevent you from adding any data. An empty file would be exactly as useful.
But no, there is no way to tell Core Data to create a new persistent store file but have that file be read only, mainly because such an operation would be nonsensical and useless.
If you have some reason to want a persistent store file which is both empty and unwritable (and if you do, please share), you would need to
Add the persistent store without the read-only flag
Call removePersistentStore:error: to remove that persistent store
Add the persistent store again, with the read-only flag.
You will now have a persistent store which contains no data, and which you are prevented from adding data to.
A simpler alternative that is just as effective is to not create the file in the first place. An empty read-only persistent store serves literally no purpose at all, so the easy approach is to just not bother creating it.

Move local Core Data to iCloud

How can I enable iCloud Core Data in an app which already uses local storage Core Data?
I've tried to use NSPersistentStoreUbiquitousContentNameKey in my persistent store options. Unfortunately, this option enables iCloud but does not transfer any of the local data to iCloud. I can't seem to get migratePersistentStore:toURL:options:withType:error: to work either. I provide the persistent store, its URL, iCloud options, etc. and it still will not migrate the existing local data to iCloud. Here's how I'm using the method:
- (void)migratePersistentStoreWithOptions:(NSDictionary *)options {
NSError *error;
self.storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:[NSString stringWithFormat:#"%#.sqlite", self.SQLiteFileName]];
NSPersistentStore *store = [self.persistentStoreCoordinator migratePersistentStore:self.persistentStoreCoordinator.persistentStores.firstObject toURL:self.storeURL options:options withType:NSSQLiteStoreType error:&error];
if (store) NSLog(#"[CoreData Manager] Store was successfully migrated");
else NSLog(#"[CoreData Manager] Error migrating persistent store: %#", error);
}
The local storage remains separate from the iCloud storage. If possible, I'd like to move the local Core Data to iCloud without manually transferring each entity.
Any ideas? I can find lots of articles, tutorials, and posts about moving back to local storage from iCloud - but I want to move from local storage to iCloud.
Here's what you'll need to do
Create a local NSPersistentStoreCoordinator
Add your existing persistent store to that coordinator and store a reference to this new returned store.
Call that handy migratePersistStore:... providing the store from #2, a URL for the store in the documents directory with a different file name and the all important options including the NSPersistentStoreUbiquitousContentNameKey key.
Here's the code, notes in-line.
NSURL *documentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
//This is the path to the new store. Note it has a different file name
NSURL *storeURL = [documentsDirectory URLByAppendingPathComponent:#"TestRemote.sqlite"];
//This is the path to the existing store
NSURL *seedStoreURL = [documentsDirectory URLByAppendingPathComponent:#"Test.sqlite"];
//You should create a new store here instead of using the one you presumably already have access to
NSPersistentStoreCoordinator *coord = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
NSError *seedStoreError;
NSDictionary *seedStoreOptions = #{ NSReadOnlyPersistentStoreOption: #YES };
NSPersistentStore *seedStore = [coord addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:seedStoreURL
options:seedStoreOptions
error:&seedStoreError];
NSDictionary *iCloudOptions = #{ NSPersistentStoreUbiquitousContentNameKey: #"MyiCloudStore" };
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//This is using an operation queue because this happens synchronously
[queue addOperationWithBlock:^{
NSError *blockError;
[coord migratePersistentStore:seedStore
toURL:storeURL
options:iCloudOptions
withType:NSSQLiteStoreType
error:&blockError];
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperationWithBlock:^{
// This will be called when the migration is done
}];
}];
Note that after you do this migration, you'll need to configure the persistent store you use with your MOC with the new URL and always include the iCloudOptions above with the NSPersistentStoreUbiquitousContentNameKey key.
This was based on Apple's documentation.
After completion, you should see a new folder in your Documents folder in the simulator folder (~/Library/Application Support/iPhone Simulator/...) labeled CoreDataUbiquitySupport. Nested deep in there is your iCloud synced sqlite store.
Tada!
EDIT: Oh and make sure you have created an iCloud entitlement and included it in your bundle. You should be able to do that all within Xcode, but you can update it on the development portal too.
Take a look at this sample app which includes code to migrate a local core data store to iCloud and back again. Best read the associated docs and build the sample apps in your environment to get them working and once they are working then try and refactor your code to use a similar approach.
Feel free to send me an email for further help. Apologies for not giving you an answer here but it can be quite a complicated issue to deal with.
http://ossh.com.au/design-and-technology/software-development/sample-library-style-ios-core-data-app-with-icloud-integration/

Updating Core Data +iCloud model with the next App version

I have to work on the next version of an Application that uses Core Data and iCloud.
iCloud has been activated in the current version using the most "modern" way:
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:#"data_cfg"
URL:storeURL
options:#{NSPersistentStoreUbiquitousContentNameKey:#"theStore"}
error:&error])
Now I need to update the store structure adding new entities, relationship and so on...
I'm totally stuck because I don't know which is the better way to update a model knowing that iCloud is synchronised with users data.
Which is the best way to perform an update like that? What should I be aware of and what I should pay attention for the most? How to migrate the current data?
Updating a model when using iCloud is the same as when not using iCloud-- with the sole exception that your changes must be ones that work with automatic lightweight migration. Apple's documentation describes the requirements for this kind of migration in detail. The basic steps are:
Create a new version of the data model, and make this version "current". (You must keep the old model around, so now you'll have two, but only one is current).
Make your model changes in the new version.
Add NSMigratePersistentStoresAutomaticallyOption and NSInferMappingModelAutomaticallyOption to your options dictionary in the code above, using #YES as the value for both.
Now when you launch the app, Core Data will compare the old and new versions of the model and (assuming your changes work for automatic lightweight migration) modify the persistent store to use the new version.
Keep in mind that iCloud syncing only works between devices that use the same version of the data model. If a user upgrades your app on one device but not another that they use, syncing will stop until they upgrade the app on other device as well.
iCloud Backup doesn't matter, by the way here is solution for installing new sqlite model.
If the data inside the Application can be recreated/downloaded from server, there is a great solution.
You don't need to setup migration stack, there is a very quick solution. The trick is to delete the old sqlite database and create a new one.
Here is the code that I used on my application update.
You need to add this in your AppDelegate.m
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"YourDatabase.sqlite"];
NSManagedObjectModel *managedObjectModel = [self managedObjectModel];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: managedObjectModel];
// Check if we already have a persistent store
if ( [[NSFileManager defaultManager] fileExistsAtPath: [storeURL path]] ) {
NSDictionary *existingPersistentStoreMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType: NSSQLiteStoreType URL: storeURL error: &error];
if ( !existingPersistentStoreMetadata ) {
// Something *really* bad has happened to the persistent store
//[NSException raise: NSInternalInconsistencyException format: #"Failed to read metadata for persistent store %#: %#", storeURL, error];
NSLog(#"Failed to read metadata for persistent store %#: %#", storeURL, error);
}
if ( ![managedObjectModel isConfiguration: nil compatibleWithStoreMetadata: existingPersistentStoreMetadata] ) {
if ( ![[NSFileManager defaultManager] removeItemAtURL: storeURL error: &error] )
NSLog(#"*** Could not delete persistent store, %#", error);
} // else the existing persistent store is compatible with the current model - nice!
} // else no database file yet
[_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:nil
error:&error];
return _persistentStoreCoordinator;
}
This code covers issues
if existing database is old one, than deleting and setting up new one.
if there is no database yet (user download only the newest version) than creating new database.
if existing database is compatible with new one, just using that.
Just change the #"YourDatabase.sqlite" to your sqliteDB filename and it will work fine.
If your'e data can't be recreated (synced from server), than You need to setup migration stack
here is link to Apple's guide https://developer.apple.com/library/mac/Documentation/Cocoa/Conceptual/CoreDataVersioning/CoreDataVersioning.pdf

What is the difference between .sqlite and .storedata

When you start a new iOS project on Xcode using core data, it initializes a database with the extension .sqlite. When you do the same thing for a new project for OSX the database has the extension .storedata.
Is there any difference between the two? thanks.
CoreData on iOS only supports an sqlite persistent store. CoreData on OS X supports multiple formats including sqlite and xml, with the default persistent store being xml-based. Thus .sqlite is an sqlite persistent store for CoreData, whereas .storedata is an xml persistent store.
To expand on the answer, an sqlite persistent store allows the model to be partially and incrementally loaded, whereas an xml persistent store only allows (requires) the model to be loaded en masse. The difference in defaults is probably explained by the differing memory availability on the two platforms. With much more memory available on a typical Mac, the overall performance is enhanced by loading everything at once.
To switch the default code to use sqlite instead of xml, edit persistentStoreCoordinator and change:
NSURL *url = [applicationFilesDirectory URLByAppendingPathComponent:#"Foo.storedata"];
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
if (![coordinator addPersistentStoreWithType:NSXMLStoreType configuration:nil URL:url options:nil error:&error]) {
to:
NSURL *url = [applicationFilesDirectory URLByAppendingPathComponent:#"Foo.sqlite"];
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
if (![coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error]) {

Coredata after Application Update

I'm trying to implement a process for my application that makes the posibility to update the app when needed, and with update I mean donwnload the newer application .ipa in the device.
The thing is that I'm using CoreData to store the server data brought at the first launch, and between the old version and the newer one I've added some entities and atributes for some old entities to the DB. That makes conflicts, as I have no idea how to handle migrations and/or any thing that can provide me the ability to re-create the data base as the structure has changed.
For now, if I update an application with the same DB structure, the app works ok, but if I modify it the app crashes, as expected.
Any thoughts?
If the data inside the Application can be recreated/downloaded from server, there is a great solution.
As I understood You are getting data from server and this is the wonderful case, that means the old data can bee recreated in new database.
You don't need to setup migration stack, there is a very quick solution. The trick is to delete the old sqlite database and create a new one.
Here is the code that I used on my application update.
You need to add this in your AppDelegate.m
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"YourDatabase.sqlite"];
NSManagedObjectModel *managedObjectModel = [self managedObjectModel];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: managedObjectModel];
// Check if we already have a persistent store
if ( [[NSFileManager defaultManager] fileExistsAtPath: [storeURL path]] ) {
NSDictionary *existingPersistentStoreMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType: NSSQLiteStoreType URL: storeURL error: &error];
if ( !existingPersistentStoreMetadata ) {
// Something *really* bad has happened to the persistent store
//[NSException raise: NSInternalInconsistencyException format: #"Failed to read metadata for persistent store %#: %#", storeURL, error];
NSLog(#"Failed to read metadata for persistent store %#: %#", storeURL, error);
}
if ( ![managedObjectModel isConfiguration: nil compatibleWithStoreMetadata: existingPersistentStoreMetadata] ) {
if ( ![[NSFileManager defaultManager] removeItemAtURL: storeURL error: &error] )
NSLog(#"*** Could not delete persistent store, %#", error);
} // else the existing persistent store is compatible with the current model - nice!
} // else no database file yet
[_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:nil
error:&error];
return _persistentStoreCoordinator;
}
This code covers issues
if existing database is old one, than deleting and setting up new one.
if there is no database yet (user download only the newest version) than creating new database.
if existing database is compatible with new one, just using that.
Just change the #"YourDatabase.sqlite" to your sqliteDB filename and it will work fine.
If you only added some entities/attributes you can use CoreData lightweight migrations:
1) Add a new version of the schema from the Editor menu with you xcdatamodeld file open.
2) Add the new entities attributes to this new schema version.
3) Set the new schema version as the active one in your xcdatamodeld options (left pane).
4) Set NSMigratePersistentStoresAutomaticallyOption and NSInferMappingModelAutomaticallyOption to true in your persistentStore initialization method in the UIApplicationDelegate.
You should now be able to run the app. The schema should be updated automatically to match your new database structure. This will also preserve the database contents, emptying it is probably something you want to do in your code, detecting the first launch of the new version.

Resources