Preparation for app release with coredata - iphone

i am coming to an end with creating version 1.0 of my new project. for the first time i am using coredata.
the application only uses 1 model, all data will be supplied by the user (so i do not load any data with the application).
of course i already working on updates for the application on different branches and see some changes in the datamodel in the future. the changes on the model will only consist of:
addition of entities
addition of attributes to existing entities
the entities do not have any relation with each other.
i have read through: iPhone app with CoreData
from there i went on to: Lightweight Migration, where i read about coredatas ability to update its model automatically if changes are minor (if i read correctly my changes are included there).
in the apple migration doc i found the code for automatic migration:
NSError *error = nil;
NSURL *storeURL = <#The URL of a persistent store#>;
NSPersistentStoreCoordinator *psc = <#The coordinator#>;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
BOOL success = [psc addPersistentStoreWithType:<#Store type#>
configuration:<#Configuration or nil#> URL:storeURL
options:options error:&error];
if (!success) {
// Handle the error.
}
my questions are the following:
where would i put this code? i found now additional information on it
do i assume that this code will only be necessary in the updated version of the app?
do i need any other preparations on my version 1.0 app to allow later motifications & updates to coredata, or do i not have to think about this in the first release?

I have this code in the method
-(NSPersistentStoreCoordinator *)persistentStoreCoordinator
There should already be code like
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
if you have let Xcode create the core data methods.
This code is only necessary in the update which introduces a new model.
As far as I know, no. This is all.

Related

Updating app that includes pre-populated database

I have an application that is using a quite large database with approximately 3.000 records. Previously i have loaded the database when starting up the app for the first time but with the number of records i have now i am implementing a pre-populated database to save time.
My question is what is happening when updating the app, from appstore, to the device will the app know that there is an updated version of the database and load the new database or continue to use the database that is already active with the app?
I am using this code in my app to use the pre-populated Core Data DB:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"database.sqlite"];
/*
Set up the store.
For the sake of illustration, provide a pre-populated default store.
*/
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"database" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
NSError *error;
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
return persistentStoreCoordinator;
}
Let's say that you app currently in the app store is version 1.0. This app downloaded a DB on its first launch.
Now, you are going to publish a new version 1.1, which will bundle the DB within the app itself.
When a user of the existing app (1.0) will upgrade to 1.1, what happens is fully under your control. Indeed, version 1.1 could check, on its first launch, for the existence of the DB in the user directory where version 1.0 installed it. In case, it is there, then version 1.1 knows it should upgrade the DB by copying it over from the resource directory.
Actually, the DB must be copied over to the user directory in any case, so by checking the you could make sure that no user data will be wiped out.
In general, you might store a version number in NSUserDefaults, so that each future version of your app has a way to know whether it is an upgrade os a new install (if the version number is there, then it is an upgrade from that specific version, otherwise it is a new install).
When you update the app, the files already created will still be there, so older versions of your app will still use the old database that you created in your old version

Core Data: Pre-populate SQLite Entity with Image

I am already pre-populating data for my application by first creating the database through core data, then populating that initialized file with SQLite Manager. Is it possible to pre-populate images in a SQLite table for use in core data as well?
My initial thought is to insert the images as a blob through SQLite Manager. Then based on this post, it looks like I would need to set the type to binary and import with UIImage initWithData:.
Is this doable, and if so, is this the appropriate method?
Pre-populating images to a SQLite database for use with Core Data turns out to be fairly trivial.
First configure your Core Data application, implementing your attribute to contain images as a "Binary" type. Build the application and navigate to your view utilizing Core Data within the application simulator. This will initialize the SQLite database as required for use with Core Data (assuming you've implemeted the persistentStoreCoordinator as follows).
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"YourDBName.sqlite"];
// Set up the store.
// For the sake of illustration, provide a pre-populated default store.
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn’t exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"YourDBName" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
NSError *error;
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
return persistentStoreCoordinator;
}
Navigate to the application data at "Users//Library/Application Support/iPhone Simulator/User/Applications/". If you sort the folder by "Date Modified," your application will have the latest date (assuming you haven't built any other applications in the mean time). Enter the application folder and the initialized < YourDBName.sqlite > will reside in the Documents folder. Copy the SQLite database to another location (like your desktop) and delete the original file (this is necessary to allow Core Data to reload the pre-populated SQLite database you are about to create).
Open < YourDBName.sqlite > with your favorite SQLite editor (the SQLite Manager plugin for Firefox is an adequate, and free, option). Add entries to your table, inserting any images as a "BLOB."
Within XCode add < YourDBName.sqlite > as an existing file. Core Data will copy this file to the application data folder the next time you launch your application if it does not already exist there (you deleted the original right?).
Access your pre-populated images within your code with [UIImage imageWithData:< DataObject >.< ImageAttributeName >
How big are your images going to be? If they are fairly large you may be better served by storing the image in the file system and keeping a reference to its location in core data.
If the images will always exist in your app then you can package them with your bundle. If not (e.g. a user can remove unwanted images) you may have to rely on pulling the images in on first use.

how to make an iPhone app tolerant of a CoreData schema change

I have an app that uses the CoreData APIs extensively. I'm developing an updated version that adds a few fields to an entity. My app crashes upon load (unless if I blow away the private storage and start again) because of the schema changes.
The problem is when customers upgrade to the new version, I wouldn't mind running an upgrade procedure on their data the first time the app loads, but I can't even get it to load because CoreData doesn't like that the schema changes.
Is there any way to sort of tell CoreData "Its ok.. don't worry about the schema change"? Because I have only added fields and haven't renamed or deleted anything.
You should probably get a copy of Marcus Zarra's Core Data book and read up on migration (Ch. 5). But, failing that, there are some basics that are good to know. First, you need both your old model (schema) and your new model in your updated app. Second, you need to make sure that the new model is tagged as being the "current model". Third, you need to make sure that you create your NSPersistentStoreCoordinator in such a way that it automatically maps from existing model (as loaded from disk) to new model.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (persistentStoreCoordinator)
return persistentStoreCoordinator;
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"MyDataStore.sqlite"]];
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
// Use mapping model
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, nil];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeUrl
options:options
error:&error])
{
[NSApp presentError:error];
}
return persistentStoreCoordinator;
}
Update
Your old model in your new app needs to be exactly the same as the model in your old app. If you are unsure that this is the case, then there are some steps that you can take to make sure. The way I do it is a bit involved - but I will outline it if/when you think that that would be helpful.

Core Data lightweight migration: Can't find or automatically infer mapping model for migration

So I created a new version of my data model, and made a previously optional field non-optional (giving it a default value). According to the documentation, this should mean my migration is eligible for lightweight, automatic migration.
I also added options that allow this when I open the store, also per the documentation:
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
When my app is starting, however, I get the following error:
"Can't find or automatically infer mapping model for migration".
Does anyone know what the problem here could be? Any help is appreciated... thanks!
You've probably looked at this, but if not ... Detecting a Lightweight Core Data Migration
In terms of other debugging code, I found this helpful:
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"MyDataStore.sqlite"]];
NSError *error = nil;
NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:storeUrl error:&error];
if (!sourceMetadata)
{
DLog(#"sourceMetadata is nil");
}
else
{
DLog(#"sourceMetadata is %#", sourceMetadata);
}
And finally, this is kind of a pain but in the Finder you can "Show Package Contents" for your app and then find a folder called .momd and within that is a file called 'VersionInfo.plist'. This has been helpful in identifying what you have and where you're trying to go.
And finally, you could try to create a mapping model and see if that works. I've wrestled with migration issues for weeks, hence the long list of desperate debugging attempts.

NSEntityMigrationPolicy subclass methods not being called

I am trying to migrate from one .xcdatamodel file to another. I have a NSEntityMigrationPolicy subclass, the name of which I have entered in xcode-> .xcmappingmodel file -> entity -> "custom Policy" field.
I run my app which successfully opens and runs the previous version of my data so I can only assume basic migration has worked. HOWEVER my NSEntityMigrationPolicy subclass methods are not being called so that I can run further migration code.
#implementation TestMigrationPolicy
- (BOOL)beginEntityMapping:(NSEntityMapping *)mapping manager:(NSMigrationManager *)manager error:(NSError * *)error
{
NSLog(#"this log is never shown!!!!");
return YES;
}
Does anyone have any ideas why my it might not be getting called? I am new to core data migration and I'm currently at a loss as to why this is not behaving as I feel it should.
If it helps, I am creating the persistent store like this..
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES],
NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES],
NSInferMappingModelAutomaticallyOption,
nil];
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSLog(#"storeUrl %#", storeUrl);
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
I know this question is old but this may help others!
It's because you have the option NSInferMappingModelAutomaticallyOption set - which means lightweight migration is being run, rather than using your mapping model. Remove this option, leaving * NSMigratePersistentStoresAutomaticallyOption* in place and all should work.
I've faced the same issue. In my case this happens because Core Data is not able to found my compiled data mapping file (a file with 'cdm' extension) in the resulting application bundle. When I manually moved that file from nested bundle to the root of application bundle (MyApp.app\NestedBundle.bundle\MyMapping.cdm -> MyApp.app\MyMapping.cdm) everything worked fine. But such file layout violates current logic of application bundle structure, so I will try to make Core Data to see my cdm file in nested bundle as well.
UPD: It seems that the best solution is to use custom initialization for migration process. Very nice example can found here - http://media.pragprog.com/titles/mzcd/code/ProgressiveMigration/AppDelegate.m. I've adopted that code for searching in all bundles and it works fine.