Multiple data models with single persistent store coordinator - ios

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.

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.

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

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.

NSPersistentStoreCoordinator with two types of persistent stores?

In an iOS app, I would like to use an NSPersistentStoreCoordinator with both an NSIncrementalStore subclass, for fetching data from a REST API, but also with an SQLite store, to save to disk. If I add both types of persistent stores to my coordinator, however, calling save: on my managed object context has no effect. If I only add the one persistent store, not of the type for my NSIcrementalStore subclass, then the save works as intended.
Is there any way to achieve this functionality?
The best solution in my experience is to have multiple managed object contexts, each having its own model.
However, there is a way to accomplish what you want:
// create the store coordinator
NSPersistentStoreCoordinator *storeCoordinator = [[NSPersistentStoreCoordinator alloc] init];
// create the first store
NSPersistentStore *firstStore = [storeCoordinator addPersistentStoreWithType: NSIncrementalStore configuration:nil URL:urlToFirstStore options:optionsForFirstStore error:&error];
// now create the second one
NSPersistentStore *secondStore = [storeCoordinator addPersistentStoreWithType:NSSQLiteStore configuration:nil URL:urlToSecondStore options:optionsForSecondStore error:&error];
// Now you have two stores and one context
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:storeCoordinator];
// and you can assign your entities to different stores like this
NSManagedObject *someObject = [[NSManagedObject alloc] initWithEntity:someEntity insertIntoManagedObjectContext:context];
// here the relevant part
[context assignObject:someObject toPersistentStore:firstStore]; // or secondStore ..
You should also check these links to get a better idea about how Core Data works:
Core Data Programming Guide - Persistent Store Coordinator
SO: Two persistent stores for one managed object context - possible?
SO: Can two managed object context share one single persistent store coordinator?
Also check the comment by TechZen in the second link about configurations and read about it in here:
Core Data Programming Guide - Configurations
and here is a nice tutorial to manage two object contexts:
Multiple Managed Object Contexts with Core Data

Unit Testing with Core Data objects in XCode 4.5 using OCUnit in iOS-Development

Is there a simple way out there to create NSManagedObjects for testing reasons without to use the managed object context created for the release Application?
I'm now into Core Data coding for a couple of weeks but still having some issues in the details ... why can't I just alloc and init objects for testing? Do I really have to handle with a second persistant store / managed object context (and which one)?
I have to test some methods written in my NSManagedObject subclasses ...
Trust me, you do NOT want to test core data objects without using a MOC. You have to do unsound things at best.
However, if you don't want to use your actual database, use an in memory store. It's very simple to set up. In fact, it's what I use for a lot of my own unit testing.
I caution you, though. There are a number of things that do not behave the same with SQL stores and in-memory stores. The most common problem will be with predicates. Read the docs to make sure your predicates are right.
I will say that during testing, you can use the in-memory MOC, but you should have a configuration that runs ALL you tests on the actual database itself to make sure it al works. For speed, maybe you use the in-memory database for normal use, and use the actual one for scheduled continuous-integration testing.
As an example, you can do something like this to create your in-memory MOC...
- (NSManagedObjectContext*)managedObjectContextWithConcurrencyType:(NSManagedObjectContextConcurrencyType)concurrencyType {
NSManagedObjectModel *mom = [NSManagedObjectModel mergedModelFromBundles:nil];
STAssertNotNil(mom, #"Can not create MOM from main bundle");
NSPersistentStoreCoordinator *psc = [[MyPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
STAssertNotNil(psc, #"Can not create persistent store coordinator");
NSPersistentStore *store = [psc addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:0];
STAssertNotNil(store, #"Can not create In-Memory persistent store");
MyManagedObjectContext *moc = [[MyManagedObjectContext alloc] initWithConcurrencyType:concurrencyType];
moc.persistentStoreCoordinator = psc;
return moc;
}

Can't merge models in managedObjectContext (basic Coredata)

I'm running into an error that seems to involve migrations, but I'm not using any migrations. Still basic Core Data stuff here.
I have an existing application to which I'm trying to add Core Data. I've put the relevant code in my App Delegate. The application is a basic tab application.
How do I access Core Data in other controllers besides the delegate? After Googling, I thought the way was to do something like this:
app = (RootAppDelegate*)[UIApplication sharedApplication].delegate;
FirstViewController.managedObjectContext = app.managedObjectContext;
When I do this I get:
* Terminating app due to uncaught exception
'NSInvalidArgumentException', reason:
'Can't merge models with two different
entities named 'Event'
What am I doing wrong here?
You've probably got two or more .xdatamodel files in the project, each with an Entity attribute. That is the entity that gets generated by the Xcode template projects so you've probably duplicated one of the template data model files.
You are getting the error in your controllers because the template app delegate does not initialize the Core Data stack until it's managedObjectContext attribute is accessed. Accessing the managedObjectContext attribute triggers the loading of the managedObjectModel attribute. The templates create the model using mergedModelFromBundles: which sweeps up all the model files in the app bundle and attempts to build a single model from them.
If you have two or more models with an entity of the same name, you get the error you see. The merge in this context has nothing to do with migration but rather the merging of multiple model files used in the same version.
Did you change your model (during the dev process) and the application is installed on device/sim with the previous model?
If so just remove the app and re-run and it should work fine.
How do I access Core Data in other controllers besides the delegate?
The preferred way to access a NSManagedObjectContext is through a NSManagedObject instance. For example:
[anEntity managedObjectContext];
You can also pass a reference of the NSManagedObjectContext to the view controllers that require it.
Update:
If you have enabled automatic migrations when you setup your persistent store coordinator, this post suggests that you would need to perform some additional steps in your managedObjectModel accessor:
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel != nil) {
return managedObjectModel;
}
NSString *path = [[NSBundle mainBundle] pathForResource:#"Event" ofType:#"momd"];
NSURL *momURL = [NSURL fileURLWithPath:path];
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL];
return managedObjectModel;
}
This is an older solution (not sure it will still do the trick).

Resources