How to add SQLite database to iOS (CoreData) app? - ios

I have an iPhone app that uses CoreData.
I want to add a pre-populated SQLite database to it. The database has 1 table (geographic locations, about 50K of them).. cities.sql
I am a bit puzzled what would be the best way to add this database?
I saw several approaches (such as locating the app folder in /Users/user/Library/... ) but my external database does not really have the same structure as apps database (no "User" table etc..).
I just want to treat this cities.sqlite as some data source.. I don't mind merging it with the apps appname.sqlite if necessary...
I am also using RestKit to manage the CoreData / API integration.
Question - how do I add this cities.sqlite to the app so I can ship the app with the pre-populated data from that database ?

Ok,
my approach to create a pre-populated db is to create a dummy app that has the goal to only populate the db you want to create. This let me to do some testing on the db without using the real app. Obviously, models should be the same.
Then, you can put it to the main bundle of your real app. Once executed, the app will check if the db exists in your document folder (for example), if not, it will copy the db from the bundle to the document folder.
NSString *storePath = [[self applicationDocumentsDirectory]
stringByAppendingPathComponent: #"yourStore.sqlite"];
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle]
pathForResource:#"yourStore" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
In Core Data Import, you can find what I mean (for the second part, but you are free to follow also the approach to populate the db).
Hope that helps.
Edit
I'll try to explain better.
You create a dummy project.
Here you can use the model (with its entities) you created in the main app. Just copy it i your dummy project.
Create some code to populate the sql store through NSManagedObjectContext stuff.
The result will consist in a sql store already populated. You don't need to touch any sql store (only through Core Data).
Move to the application folder directory into the App Simulator, copy the store and put it in your main application bundle.

EDIT: If you are working with plain SQLite database, you will need to migrate the data to CoreData-friendly persistent store. To do this you can use sqlite library to read the data. You can do this in app directly, or you write some ulitity app for this. After you get the SQLite Core Data persistent store, follow my original post:
With Core Data you can have multiple SQLite stores combined into one context. Just add two persistent stores to your NSPersistentStoreCoordinator.
[coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:mainSQLiteURL // URL for your main DB
options:nil
error:nil];
[coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:citiesSQLiteURL // URL for the additional DB
options:nil
error:nil];
After this, when you create NSFetchRequest with entity City (I don't know your entity name) it will return cities from both SQLite files.
In case you want to just search one of the stores, you can set -setAffectedStores: of your fetch request. Also, when you insert new City object, you will need to specify the persistent store by calling -assignObject:toPersistentStore: on your context. Otherwise it will get confused about where to save the new city.
Or just merge those two stores to a single file.

Related

Remove core data model on iOS app update

My question is related to migration. I cannot do a lightweight migration as there are a lot of changes with attribute types and new relationships. I don't have time for a heavy weight migration since the code is not mine and needs faster delivery.
The workaround, which could work is when the app is upgraded, app should remove the old data and data model as the data is of no use and can be downloaded from the server again. On the app did finish launching, get the .db URL and just remove it and recreate it for the very first time after the upgrade?
After some research, all the methods are pointed to light weight migration. If there is a better way please assist.
-(void) removeCoreDataAndReset{
NSError *error;
NSPersistentStoreCoordinator *storeCoordinator = storeCordinator;
for (NSPersistentStore *store in storeCoordinator.persistentStores) {
[storeCoordinator removePersistentStore:store error:&error];
[[NSFileManager defaultManager] removeItemAtPath:store.URL.path error:&error];
}
// Initialise managedobjectcontext , Store Coordinator etc
}
Reinitialise all after this method as you do in statrt
To remove the persistent store, you need to remove:
The actual persistent store file. This is located wherever you put it. You tell Core Data where it is when you call addPersistentStoreWithType:configuration:URL:options:error:, so if you're not sure, check there.
The journal files. These will have the same name as the persistent store file, but with -wal and -shm added to the end. This is very important, because in most cases nearly all of the existing data is in these files.
You can remove files with methods on NSFileManager. If you do this, do it before accessing Core Data in any way, i.e. before creating any Core Data objects of any kind.

Multiple data models with single persistent store coordinator

I inherited an iOS project that uses Core Data. This project has 8 different data models, no need to say that the project is not that big and that I can not see any good reason for splitting the entities over so many data models.
I am trying to use Encrypted Core Data with the current data models and persistent store coordinators and it is not working at all. Every data model is initialized like this:
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"First"
withExtension:#"momd"];
self.model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
// Coordinator
//NSPersistentStoreCoordinator *psc = [EncryptedStore makeStore: self.model passcode: #"pass"];
[[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.model];
NSURL *storeURL = [[[AppDelegate appDelegate] applicationDocumentsDirectory] URLByAppendingPathComponent: #"First.sqlite"];
NSError *error = nil;
[psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:nil
error:&error];
NSManagedObjectContextConcurrencyType ccType = NSMainQueueConcurrencyType;
self.context = [[NSManagedObjectContext alloc] initWithConcurrencyType:ccType];
[self.context setPersistentStoreCoordinator:psc];
So every data model has its own managed object model and its own persistent store coordinator with its persistent store and context.
What I see is that Encrypted Core Data (ECD) is only creating the tables in the firstly created persistent store. My suspicion is that ECD only handles the persistent stores added to a single coordinator. Based on that assumption I am wondering if it is possible to create a single coordinator and add several stores to it.
I am not that familiar with Core Data but I can't see how that would be possible since the coordinator is initialized with the managed object model (that points to a specific data model file containing only a set of the total number of entities in the project).
Any ideas? I really would like to avoid merging all the data models into a single one in order to use a single managed object model and coordinator (Actually I would like to do it but I am sure it would break
everything and I don't really have to time for that right now).
You can't use a single persistent store coordinator without merging the models. However, you don't have to edit your data models-- you can merge them at run time. NSManagedObjectModel offers a couple of different ways to merge multiple models into a single unified model. If you load each model independently and merge them in code, you get a single NSManagedObjectModel representing the combined model from each model file. You could then use that combined model with a single persistent store coordinator.
If you're still using multiple model files, you can add each one separately. This raises a complication though-- how will Core Data know which model file to use when you create a new model object instance? You would have to use the assignObject:toPersistentStore: method on NSManagedObjectContext to tell it which one to use. Every time you create a new instance, you do this as well. This means that you need to keep references to the NSPersistentStore instances for each file, and know which to use in every case.
I should add that I have not used encrypted Core Data so I don't know if this will solve your real problem. This approach will allow multiple model files and multiple persistent stores with a single coordinator, though.

CoreData: How to correctly use migratePersistentStore to create a backup copy

I'm really struggling with this. I'm trying to create a backup of my active core data base. According to Apple the best option is not to use the File Manager but the method migratePersistentStore. However I don't really understand this. I have my PersistentStoreCoordinator in my AppDelegate. So if I'm migrating the persistent store my coordinator will lose it after successfully moving it correctly? So the store is now just at the new location but not at the old anymore? So do you have any example program code for this of how my app still could keep running with the original copy?
Or can't i just copy all files using the filemanager with the same prefix instead of migrating?! So much easier...
You can create a separate NSPersistentStoreCoordinator for migration only, and continue using your regular one for CoreData stack. Also you can use NSMigrationManager for migration:
NSMigrationManager* manager = [[NSMigrationManager alloc] initWithSourceModel:sourceModel
destinationModel:targetModel];
BOOL migratedSuccessfully = [manager migrateStoreFromURL:sourceStoreURL
type:type
options:nil
withMappingModel:mappingModel
toDestinationURL:destinationStoreURL
destinationType:type
destinationOptions:nil
error:error];
Also I'm not sure you can migrate when your DB is opened, probably you'll need to lock it or something.

Is it possible to save a NSManagedObjectModel?

I have the following requirement: create and populate a SQLite database with data from a .xml file, this file can have a different structure any time so I cannot create the NSManagedObjectModel with Xcode, it must be at runtime. I've successfully created a NSManagedObjectModel programmatically, created the persistent store and populated the sqlite file with data from the .xml . However, the next time I use the app the persistent store is not compatible with the NSManagedObjectModel (I don't want to recreate the Model every time I run the app, just the first time). Is there any way to save the model I created programmatically and load it the next time it is needed? All I can see in the examples are models being loaded from the NSBundle.
Is there any way to save the model I created programmatically and load it the next time it is needed?
Yes. NSManagedObjectModel conforms to NSCoding, which means that you can easily convert it to/from NSData, and saving and reading NSData is easy.
To save a model:
NSString *modelPath = // path where you want to save
NSData *modelData = [NSKeyedArchiver archivedDataWithRootObject:self.managedObjectModel];
[modelData writeToFile:modelPath atomically:YES];
To read a saved model:
if ([[NSFileManager defaultManager] fileExistsAtPath:modelPath]) {
NSData *savedModelData = [NSData dataWithContentsOfFile:modelPath];
NSManagedObjectModel *savedModel = [NSKeyedUnarchiver unarchiveObjectWithData:savedModelData];
}
I'm not sure if you are saying that the data in the xml file is changing each time or what. It sounds like you are referring to the data, not the data model. I can't answer specifically, but I would take the approach as follows.
If the data in the xml file is structured the same or close to the same each time, I would create a data model to match that.
Then I would write some sort of parser class that would read the xml and parse it into the Core Data data store according to you "ManagedObjectModel" or data model.
I have seen the error you are talking about when you change the datastore outside of Core Data. You need to let Core Data handle all the reading and writing to the data store or else Core Data will tell you basically that "Your Persistent Store was created or altered by something other than your ManagedObjectModel". I think this is what is happening.
I know I am not using the terminology exactly as Core Data puts it, but Core Data is confusing and I'm trying to convey the message and understanding.
I would also look in to using MagicalRecord. It Drastically makes Core Data easier to work with and there is a great tutorial on www.raywenderlich.com which you can find Here
I really hope this helps you out some. If not, please post some sample code or maybe an example of that xml you are referring to.
Good Luck

iOS - Keep the old sqlite database while updating to new version

I've found some other question but I didn't get any clear idea how to keep the data from old database while application update in ios.
Case 1:
Can I keep the old database?
if Case 1 is YES:
Can I insert new column or doing any changes in the old database and will it be safe?
if case 1 is NO:
Can I get the old database data in new database? Will the old database will be removed?
Case 2: If I give a different name to new data base (it'll be included in bundle)? If giving a new name keeps the old database can I delete the old database programatically?
What will be the best practice? Give a new name to database file for keeping the old one and then copy the old to to new database and delete the old database file? Just start using the old one?
Looking for help.. :)
Case 1: Can I keep the old database?
Yes, updating your application won't delete the database file stored in the documents directory. (But if it is in your bundle, it'll be removed)
if Case 1 is YES: Can I insert new column or doing any changes in the old database and will it be safe?
That'll depend on your implementation. You can use ALTER TABLE query for adding or removing column. You need to handle the new changes in your code, else it can cause problems.
Adding a column won't cause any issues in normal scenarios (Depends on your insert query statements and new field constraints)
It's fairly Case 1 as assuming you have copy your .sqlite file to document folder and when you update the application it will look for the database the can update its table and update its database without loosing anything.
If I understand your question (you're looking to update a model that's already deployed via the App Store), yes you can perform upgrades to an existing model using the .xcdatamodeld format in Xcode. The Core Data Versioning doc from Apple covers this topic comprehensively.
This can be a fiddly process, if you have precious user data stored in your model, you'll want to test this exhaustively before pushing out your updates.
To add a new version to your model;
Select your xcdatamodel file (e.g. model.xcdatamodel)
Click Editor > Add Model Version
Name the new model version (e.g. Model 2), based on Model 1
Open the Utilities pane
Select 'Model 2' for the Current Model Version
Then add this method to your controller class implementation file, to help make small changes to the data model (changing field types, adding columns etc).
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"Model.sqlite"];
NSError *error = nil;
// for performing minor changes to the coredata database
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],
NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES],
NSInferMappingModelAutomaticallyOption, nil];
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
Also, worth noting: Apple recommends you only store user relevant files in the Documents folder. SQLite database files and similar should be stored in /Library/Application Support.

Resources