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).
Related
Platform
iOS 10, Xcode 8.3.3
Background
I have built a Notes application that takes advantage of Core Data and I'd like to use this app in my next application, which will also use Core Data. For simplicity, lets call my next application, "ListApp", and my notes application, "NotesApp". This ListApp has list items each of which can have one or more notes.
Here's what I've done so far:
Removed all unnecessary files from the NotesApp and compiled a "NotesApp Framework".
Linked the NotesApp Framework to the ListApp.
Designed the Core Data Model for ListApp. Specifically, I created an entity called "ListItem" and an entity called "Note". The ListItem has a to-many relationship with the Note (one list item can have multiple notes). The Note entity contains a "noteID" field to reference the note in the NotesApp model and, of course, the inverse relationship.
Problem
I need to form a "relationship" between an entity in the ListApp model and an entity in the NotesApp model.
I've researched configurations and that seems to be more for storing objects in the same model to different persistent stores unless there's something I'm missing. So, that doesn't help.
Then, I found that fetched properties can be used to form weak relationships between multiple stores. So, that doesn't help either.
Next, I found in the documentation that there's a method called NSManagedObjectModel.mergedModel(from:) so I'm assuming this is possible. Or maybe that's only for migration?
That's where I'm stuck.
Reason
I'd rather not redesign everything in the NotesApp model in to the ListApp model. I prefer to keep everything separate.
Questions
Is there a way to form a relationship between two entities in different models? Should I just add a function in the ListItem entity class to fetch the notes in the NotesApp model manually? Am I even going down the right path or is there a better option?
NOTE: What I mean by "relationship" is the ability to call on a property in the ListItem entity to fetch the notes and somehow "relate" specific notes to a specific ListItem.
P.S. If you know of any pitfalls, have any general advice, or know of any reading material please feel free to let me know.
Also, I've been researching this topic for a couple of hours and I can't seem to find anything about it. I'm assuming that's either because it's not possible, it's a terrible practice, or I'm not using the right keywords.
If anyone needs any more information feel free to let me know!
I think you're saying your bundle will have one model that contains the List entity and another that contains the Note entity. You can merge and tweak managed object models, as you suggested, and use the resulting managed object model which you have in code.
If you're creating your Core Data stack in code (that is, you are not using the new NSPersistentContainer), it is easy to splice in a custom managed object model.
If you are using NSPersistentContainer, you'd have to subclass it and override managedObjectModel(). I can't find any documentation saying you can't do that, but I wouldn't bet on that.
If you're document-based, overriding UIDocument's managedObjectModel should work.
To create your custom managed object model, merge your models using NSManagedObjectModel.mergedModel(from:). Then, get the Note and List entity decriptions, get the properties of each, mutate, add your new relationships, then set them back into the model. You would only do this on the first run; cache your custom managed model to a ivar for subsequent runs.
Hmmmm. What I've just described, essentially tearing apart, tweaking and reassembling that managed object model, is going to be quite a few lines of code. If this is a really just a simple "notes and lists" app, and if these are the only these two entities, it would probably be less code to ditch those .mom files and create the whole managed object model from scratch, in code. It's not that hard. Put on your Objective-C glasses and look at the managedObjectModel() function in main.m of Apple's old Core Data Utility sample project.
Alright, so turns out I had a slight misunderstanding of the Core Data Stack but, this is an extremely simple task. I was able to get this to work very easily based on some research and #Jerry Krinock's answer.
Create a framework containing the needed files from the NotesApp.
Link the framework to the ListApp.
Grab mutable references to the ListApp and NotesApp NSManagedObjectModel.
Programmatically add a NSRelationshipDescription between the ListItem entity in the ListApp Model and the Note entity in the NotesApp Model (and vice versa for the inverse).
Create a NSManagedObjectModel by merging the ListApp and NotesApp models.
NOTE: As #Jerry Krinock mentioned this only needs to be done once since we are merging the two models together and storing them in the same persistent store. This is the same as doing it through the CoreData Model Builder UI except programmatically since it doesn't support referencing entities from separate models (at least not that I know of or could find).
References:
Core Data Programming Guide
Core Data stack
Universal Cocoa Touch Frameworks for iOS8 – (Remix)
Adding relationships in NSManagedObjectModel to programmatically created NSEntityDescription
Objective-C:
NSManagedObjectModel * listModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:[self listModelURL]] mutableCopy];
NSManagedObjectModel * notesModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:[self notesModelURL]] mutableCopy];
NSEntityDescription * listEntity = [listModel.entitiesByName objectForKey:NSStringFromClass([JBListItemMO class])];
// The framework name is prepended to the class name. Remove it before getting the note's entityDescription.
NSString * noteClassName = [NSStringFromClass([JPSNoteMO class]) componentsSeparatedByString:#"."].lastObject;
NSEntityDescription * noteEntity = [notesModel.entitiesByName objectForKey:noteClassName];
NSRelationshipDescription * whichListRelationship = [[NSRelationshipDescription alloc] init];
whichListRelationship.minCount = 0;
whichListRelationship.maxCount = 1;
whichListRelationship.optional = NO;
whichListRelationship.name = #"whichList";
whichListRelationship.destinationEntity = listEntity;
whichListRelationship.deleteRule = NSNullifyDeleteRule;
NSRelationshipDescription * notesRelationship = [[NSRelationshipDescription alloc] init];
notesRelationship.ordered = NO;
notesRelationship.maxCount = 0;
notesRelationship.minCount = 0;
notesRelationship.optional = YES;
notesRelationship.name = #"notes";
notesRelationship.destinationEntity = noteEntity;
notesRelationship.deleteRule = NSCascadeDeleteRule;
notesRelationship.inverseRelationship = whichListRelationship;
whichListRelationship.inverseRelationship = notesRelationship;
listEntity.properties = [listEntity.properties arrayByAddingObject: notesRelationship];
noteEntity.properties = [noteEntity.properties arrayByAddingObject: whichListRelationship];
self.managedObjectModel = [NSManagedObjectModel modelByMergingModels:#[listModel, notesModel]];
I'll post the Swift 3 code when I've finished converting my CoreDataStack class.
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.
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.
So I have version 1 of my Core Data app in the App Store and now I started working on version 2.
I have some small changes to my data base model, and some custom code I need to run after those changes to complete the upgrade from version 1 to version 2.
I can use Core Data's lightweight migration to deal with the model change, and I can run my custom code after the migration is done.
The problem is, i'm not sure what will happen in the future, when I'll build version 3,4,5...
suppose this is the case:
version 1 to version 2 - use lightweight migration
version 2 to version 3 - use custom migration with model mapping
version 3 to version 4 - use lightweight migration again
and so on..
I'm not sure how to build a mechanism that deal with this mix of lightweight and custom migrations.
I couldn't find any code online or in Core Data docs that talk about this issue, I mean this is a very common issue for most of the Core Data apps out there, is there any best practice examples for this issue?
Two methods are written below:
I personally like this method since it's just genius IMO, you can tell from the detail of my explanation why I'm so excited about this method. This method has been written in Marcus Zarra's core data second edition book. It's an implementation for an automatic Progressive migration process for both heavy and lightweight use.
A method I came up with for a possible implementation after the OP asked if there was a way to avoid having to use mapping models for each migration upgrade
Method 1
This is where Progressive Migration comes in. You create mapping models for each version upgrade that will deal between two model versions. A mapping model will tell the migration manager how to map an old version of the data store to the new version of the data store. So a mapping model requires a source and a destination.
You then have as many mapping models required that will cover all the version upgrade increments. So if you have 4 versions, youll have 3 mapping models that will aid the transition between each version upgrade. In the end youll have them named: V1toV2MappingModel, V2to3MappingModel, V3toV4MappingModel.
In each mapping model you can then toggle the options to let the migration manager know how you wish to migrate. Whether its via a custom policy or a normal copy migration. So you say that for v1 to v2 you require a light weight migration, simply select the appropriate mapping model, you then use the model/mapping editor to make the desired connections/mappings between old attributes to new attributes and if its a custom migration required, then go to the right mapping model, choose the product entity mapping that you wish to customise the migration for, then in the entitymapping inspector you'll see that you can apply a custom migration policy, just copy the name of your migration policy say for example MigrationPolicyV2toV3 since its that particular version that you wanted to have customised.
So in the image above you can see on the left hand side in the name of the mapping model that it's for Version 1 to Version 2. Notice that the attribute mapping for the ProductToProduct entity mapping is empty - in the middle. This is because if you look on the right of the image in the entity mapping inspector where it says Custom Policy, I've put the name of my migration policy. This then lets the migration manager know (during the migration process) how to map the attributes and values from the source to the destination - version 1 to version 2 - and it knows this because of the migration policy inputted. This also causes the value type to be changed to custom. letting you know its going to be a custom migration when it comes to the ProductToProduct entity mapping.
Then you have to define in your migration policy which will determine how you wish to copy the values over. This is where you do your custom stuff.
As you can see from the image above, its the custom migration policy I've set for the ProductToProduct entity mapping. You'll notice that I'm not actually doing anything custom, all this could have been done without the migration policy and could have been achieved by simply doing a copy migration (lightweight migration) by adjusting a few values in the entityMapping inspector and adjusting the Attribute mapping values in the image before. I only did all of this migration policy custom stuff just as an exercise so that I can learn and be prepared for the future just INCASE I ever needed to do a heavy migration. Better learn it now than later hey ;)
That's it for doing custom stuff. I suggest you read up on the following apple developer reference on NSEntityMigrationPolicy and the methods required to do more custom stuff so that you know how to have full control throughout the migration process whenever a particular revision upgrade requires some or full custom migration.
And for any custom/heavyweight migrations - where in my case I use a migration policy so that I can do some custom code stuff during the migration from V2 to V3 in my data store - then you create something called a 'migration policy' so that THAT mapping model will adhere to the custom migration rules your specify.
And you simply apply all the appropriate transitions/mappings for each mapping model so that the migration manager knows how to upgrade from one store to the next.
All you need then is some recursive code that will look at the existing store's meta data to determine whether it's compatible with the most current version, and if its not, it will then do a recursive migration for you, automatically, following the rules from each mapping model as it upgrades through the versions until the store is up to date with the current version.
So this way you can accommodate all users with any version and have them brought up to speed to the current version's store. So if a user is at version 1, it will recursively go from V1 to V2, then migrate to v3 all the way up to your current version. The same applies if the user has any other version.
This does mean that it will take a little longer in the migration process, but its a good way of migrating all your users no matter which version they have.
To find this progressive migration code, you need to read a book called Core Data 2nd Edition - Data storage and management for iOS, OS X, and iCloud, Chapter 3 Versioning and Migration sub chapter 3.6 Progressive Data Migration (An Academic Exercise) from pages 54 to 59.
He talks you through the code,and tells you step by step how to write the progressivelyMigrateURL:ofType:toModel:error: method. Once you've written the method with him, he then tells you how to call this method on application startup so that your users can have their stores automatically migrated progressively too.
So you should probably write the method first, then follow my steps up above or you can read through migration policies in the subchapters before.
I practically learned all this in the last 48 hours and have it all up and running now. Able to do lightweight migrations for some versions and then have custom migrations for my other versions all done automatically.
Method 2 - Another solution albeit more cumbersome IMO: You can always have lightweight migration setup bearing in mind that you apparently can achieve even complex situations with lightweight migration. But in the instances where heavy migration is required for a specific version upgrade, you can always do if statements where you can check the current store, and if and ONLY if the current version of the persistent store matches an upgrade where heavy migration is required, you then tell the migration manager to perform a heavy migration and to follow a mapping model with a migration policy for that instance only, and then resume lightweight migration if there are more version upgrades to do to get the persistent store to the most recent model version.
If you can refetch data from a server or anything like that, the best way to deal with this is by removing your old model and immediately recreating a new one with the current structure. This is embedded in a singleton class and is called every time a new managedobjectcontext has to be created. My code for this is the following:
- (NSManagedObjectContext *)managedObjectContext {
if (__managedObjectContext != nil) {
return __managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
__managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[__managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return __managedObjectContext;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (__persistentStoreCoordinator != nil) {
return __persistentStoreCoordinator;
}
NSURL *storeURL = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent:#"YOURDB.sqlite"];
NSError *error = nil;
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
if (error.code == 134100) {
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];
}
if ( ![[self managedObjectModel] isConfiguration: nil compatibleWithStoreMetadata: existingPersistentStoreMetadata] ) {
if (![[NSFileManager defaultManager] removeItemAtURL: storeURL error: &error] ) {
NSLog(#"*** Could not delete persistent store, %#", error);
abort();
} else {
[__persistentStoreCoordinator addPersistentStoreWithType: NSSQLiteStoreType configuration: nil URL: storeURL options: nil error: &error];
}
}
}
} else {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
return __persistentStoreCoordinator;
}
The simplest way is keeping old attributes in new version and not using them anymore. if you need map them to new attributes, just do the mapping after lightweight DB upgrade. The world will keep peaceful.
The benefits:
no need to make mapping model.
keep using light weight upgrade.
Let me give you example:
let's say we have a entity named Student with attribute name in V1, and we gonna map it to a new attribute firstName in V2.
and we have a method as below, so then we can call this method after regular lightweight CoreData upgrade, enjoy!
- (void)migrateStudentRecords {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Student"];
NSArray *result = [defaultContext executeFetchRequest:fetchRequest error:error];
if (result && result.count > 0) {
[result enumerateObjectsUsingBlock:^(Student *_Nonnull newRecord, NSUInteger idx, BOOL * _Nonnull stop) {
newRecord.firstName = newRecord.name;
newRecord.name = nil;
}];
if (defaultContext.hasChanges) {
[defaultContext save:error];
}
}
}
For my iOS application using CoreData, I need to have a model where certain entities properties vary between the iPhone and iPad version of the app.
To achieve this, I load a NSManagedObjectModel from a momd file located in my application bundle, using initWithContentsOfURL: . But, before the model is actually used by the storeCoordinator, I modify some entities programmatically in the model (based on information I store in a device specific plist).
This used to work flawlessly on iOS4. As Apple states in its documentation,
Managed object models are editable until they are used by an object graph manager (a managed object context or a persistent store coordinator).
This does not seem to be the case in iOS5 anymore (although the documentation still states this). As soon as the model is created, with initWithContentsOfURL: for example, any attempt to modify throws an 'NSInternalInconsistencyException', reason: 'Can't modify an immutable model.' If I print the description of the model object in the debugger just after it was created, it reads 'isEditable 0', where the same reads 'isEditable 1' when running the same code on iOS4.
Using "copy" on the model the create an editable copy, as suggested by Apple, also returns a model with "isEditable 0".
I see two potential explanations for this :
Bug . I couldn't find a matching bugreport on openradar, I will file one if necessary.
iCloud integration. I'm not very familiar with iCloud APIs yet, but I know some kind of integration with CoreData can be set up. I imagine that maybe some automatic coordinator could access my model at the time it's created, rendering it uneditable.
I'll continue digging into these options, but if anybody has experience with this, it would be greatly appreciated.
It's a bug or an undocumented change. I ran this test code:
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"TestManageObjectModelEdits" withExtension:#"momd"];
NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSEntityDescription *e=[[NSEntityDescription alloc] init];
[e setName:#"PEntity"];
NSArray *a=[mom entities];
NSArray *b=[a arrayByAddingObject:e];
[mom setEntities:b];
NSLog(#"mom = %#",mom);
… under iOS 4.3 and 5.0. It works under 4.3 and throws an error saying it can't modify the model file under 5.0.
I filed a radar on this very issue on Oct 5 and received the following response today:
You can get a mutable copy of the model by calling -mutableCopy on the NSManagedObjectModel instance.
This worked on the iOS 5 simulator, I haven't yet tested on a device. Didn't try that myself because NSManagedObjectModel doesn't conform to NSMutableCopying (according to the documentation), and the header file doesn't mention -(id)mutableCopy.
I had worked around it back in October by creating a fresh NSManagedObjectModel by merging the one from the momd file with an empty NSManagedObjectModel. I suppose the models from momd files now are internally immutable, but the others (from copy or merge) are actually mutable.