I'm getting this crash log when I create a new version of my Core Data Model and then restart the app:
*** -[NSRelationshipDescription name]: message sent to deallocated instance 0x1d032e240
It seems the model migration doesn't work whereas I'm using the NSMigratePersistentStoresAutomaticallyOption and NSInferMappingModelAutomaticallyOption store options...
Can someone help me?
Thanks!
EDIT: the changes I've made to my model are just the addition of a new attribute (Boolean as NSNumber) to en existant entity
EDIT: here is the Core Data initialization code:
- (BOOL) loadCatalog
{
// Unload any existing store
[self _unloadCurrentStore];
// Create a new managed object context
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
_managedObjectContext.persistentStoreCoordinator = _persistentStoreCoordinator;
// Create the persistent store
NSURL *storeURL = [[AppDelegate sharedDelegate].catalogDirectoryURL URLByAppendingPathComponent:#"Store.sqlite"];
NSDictionary *storeOptions = #{ NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES };
return ([_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:storeOptions error:nil] != nil);
}
Related
I have added new attribute and I need to migrate core data. As a result, I do like this.
Bus.xcDataModel >> Add model version
Then, I add new attribute. I change persistent store coordinator option as well like this.
#{NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES}
But when I add that new attribute, it show me like this. How shall I do?
-[BusService setBus_wap:]: unrecognized selector sent to instance 0x1742a7320
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator_busservice
{
if (_persistentStoreCoordinator_busservice != nil)
return _persistentStoreCoordinator_busservice;
NSURL *applicationDocumentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *storeURL = [applicationDocumentsDirectory URLByAppendingPathComponent:#"Busservice_new.sqlite"];
if (![[NSFileManager defaultManager] fileExistsAtPath:[storeURL path]] &&
!self.preloadEnabled)
{
NSURL *preloadURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"Busservice_new" ofType:#"sqlite"]];
NSError* err = nil;
if (![[NSFileManager defaultManager] copyItemAtURL:preloadURL toURL:storeURL error:&err])
DLog(#"Oops, could not copy preloaded data");
}
NSError *error = nil;
_persistentStoreCoordinator_busservice = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator_busservice addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:#{NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES} error:&error])
{
DLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator_busservice;
}
From that error it looks like you added a new attribute to your BusService entity in Core Data, but did not add the attribute to your BusService class. If you want to use accessor methods for this new attribute, you need to update the class as well. Or you can leave the class alone but use setValue:forKey: to assign values to the new attribute.
I'm tearing my hair out with this one.
I've got an App on iTunes which I added iCloud support to end of last year (Oct '13) on iOS7.0
This week I decided to write a new functional for the App which requires a new entity in the xcdatamodel. A very simple change/addition. Should have no impact on the current data set.
I create a new v2 xcdatamodel and set it to Current Model version, compile and run and it works fine if I've got iCloud switch off on my iPad. I see previous saved data.
Run it again with iCloud switch on and I get a blank table with no data.
No error messages, nothing.
Hoping someone can throw some light on what I've done wrong here:
- (NSManagedObjectModel *)managedObjectModel {
if (__managedObjectModel != nil) {
return __managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"UserData" withExtension:#"momd"];
__managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return __managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
NSError *error = nil;
BOOL success = NO;
if((__persistentStoreCoordinator != nil)) {
return __persistentStoreCoordinator;
}
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
NSPersistentStoreCoordinator *psc = __persistentStoreCoordinator;
NSString *iCloudEnabledAppID = #"C3FUPX46ZG~com~software~App";
NSString *dataFileName = #"UserData.sqlite";
NSString *iCloudDataDirectoryName = #"CoreData.nosync";
NSString *iCloudLogsDirectoryName = #"CoreDataLogs";
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *localStore = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:dataFileName];
NSURL *iCloud = [fileManager URLForUbiquityContainerIdentifier:nil];
if (iCloud && ([UserDefaults getIsiCloudOn])) {
// This iCloud storage fails to migrate.
NSURL *iCloudLogsPath = [NSURL fileURLWithPath:[[iCloud path] stringByAppendingPathComponent:iCloudLogsDirectoryName]];
if([fileManager fileExistsAtPath:[[iCloud path] stringByAppendingPathComponent:iCloudDataDirectoryName]] == NO) {
NSError *fileSystemError;
[fileManager createDirectoryAtPath:[[iCloud path] stringByAppendingPathComponent:iCloudDataDirectoryName]
withIntermediateDirectories:YES
attributes:nil
error:&fileSystemError];
if(fileSystemError != nil) {
NSLog(#"Error creating database directory %#", fileSystemError);
}
}
NSString *iCloudData = [[[iCloud path]
stringByAppendingPathComponent:iCloudDataDirectoryName]
stringByAppendingPathComponent:dataFileName];
NSDictionary *options = #{NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES,
NSPersistentStoreUbiquitousContentNameKey : iCloudEnabledAppID,
NSPersistentStoreUbiquitousContentURLKey : iCloudLogsPath
};
success = [psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[NSURL fileURLWithPath:iCloudData]
options:options
error:&error];
if (!success) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
} else {
// This local storage migrates automatically just fine.
NSDictionary *options = #{NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES
};
success = [psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:localStore
options:options
error:&error];
if (!success) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:kCoreDataChangeNotification object:self userInfo:nil];
});
return __persistentStoreCoordinator;
}
UPDATE: Switched on core data debugging & iCloud debugging/logging.
Migration works for both local & iCloud. Logs are the same, ending with:
CoreData: annotation: (migration) inferring a mapping model between data models with...
CoreData: annotation: (migration) in-place migration completed succeessfully in 0.03 seconds
-PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:: CoreData: Ubiquity: mobile~F9AC6EB1
Using local storage: 1
With iCloud storage & debugging on it seems to cause a delay and I briefly see my saved data for about 10seconds when it then disappears.
Just before it disappears the debugs spit out:
CoreData: annotation: (migration) inferring a mapping model between data models with...
Using local storage: 0
The iCloud logs are enormous which is why I'm not posting them here. From what I can see I have over 400 log files and iCloud seems to be doing some sort of syncing. If I leave the App and iPad open and on for a few hours I still see an empty data set. So it's not a case of waiting for a sync catch up. I'm still at a loss even with the debugs on....
I'm trying to post new managed object to the server by using rest kit, but I don't know what I'm doing wrong. I'm getting exception like the following:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'RKRequestDescriptor objects must be initialized with a mapping whose target class is NSMutableDictionary, got 'Users' (see [RKObjectMapping requestMapping])'
I was looking for solution in stack overflow posts like this one
This is my entity mapping method from MappingProvider class:
+(RKMapping *)usersMapping
{
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:#"Users" inManagedObjectStore:[[DateModel sharedDataModel]objectStore]];
[mapping addAttributeMappingsFromDictionary:#{
#"id": #"user_id",
#"address1": #"address1",
#"address2": #"address2",
#"created_at":#"created_at",
#"updated_at": #"updated_at",
#"email": #"email",
#"name":#"name",
#"password_digest": #"password_digest",
#"phone_no": #"phone_no",
#"postcode":#"postcode",
#"remember_token":#"remember_token",
#"user_type": #"user_type",
#"apns_token":#"apns_token"
}
];
[mapping addRelationshipMappingWithSourceKeyPath:#"admins" mapping:[MappingProvider adminsMapping]];
[mapping addRelationshipMappingWithSourceKeyPath:#"carers" mapping:[MappingProvider carersMapping]];
[mapping addRelationshipMappingWithSourceKeyPath:#"customers" mapping:[MappingProvider customersMapping]];
[mapping addRelationshipMappingWithSourceKeyPath:#"userWearers" mapping:[MappingProvider customersMapping]];
return mapping;
This is the method called when user fill all the textfields and click the register button:
-(void)registerUser
{RKResponseDescriptor *userResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[MappingProvider usersMapping] method:RKRequestMethodPOST pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
//here Xcode return exception
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:[MappingProvider usersMapping] objectClass:[Users class] rootKeyPath:nil method:RKRequestMethodPOST];
[[DateModel sharedDataModel]addResponseDescriptor:userResponseDescriptor];
[[DateModel sharedDataModel]addRequestDescriptor:requestDescriptor];
RKManagedObjectStore *objectStore = [[DateModel sharedDataModel]objectStore];
Users *user = [NSEntityDescription insertNewObjectForEntityForName:#"Users" inManagedObjectContext:objectStore.mainQueueManagedObjectContext];
user.email = _email;
user.password_digest =_password;
user.name = _name;
user.address1 = _address;
user.postcode = [NSNumber numberWithInteger:[_postcode integerValue]];
user.phone_no = [NSNumber numberWithInteger:[_mobileNumber integerValue]];
[[RKObjectManager sharedManager] postObject:user path:nil parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"Success saving user");
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failure saving user: %#", error.localizedDescription);
}];
}
Date-model setup method:
- (void)setup {
self.objectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSString *path = [RKApplicationDataDirectory() stringByAppendingPathComponent:#"Data.sqlite"];
NSLog(#"Setting up store at %#", path);
[self.objectStore addSQLitePersistentStoreAtPath:path
fromSeedDatabaseAtPath:nil
withConfiguration:nil
options:[self optionsForSqliteStore]
error:nil];
[self.objectStore createManagedObjectContexts];
//Configure a managed object cache to ensure we do not create duplicate objects
self.objectStore.managedObjectCache =[[RKInMemoryManagedObjectCache alloc]initWithManagedObjectContext:self.objectStore.persistentStoreManagedObjectContext];
// Set the default store shared instance
[RKManagedObjectStore setDefaultStore:self.objectStore];
}
The purpose of the request descriptor is to convert your custom object into an NSMutableDictionary so that it can be serialised and sent. The mapping you're currently using is for converting into a Users object, so you need to use a different mapping.
RestKit has a convenience method that you can use:
... requestDescriptorWithMapping:[[MappingProvider usersMapping] inverseMapping] ...
In upgrading my application from v1 to v2, I've made a few small changes to the Core Data Model. The changes are just adding new attributes to the models.
I have versioned the data model with the before and after changes and implemented the following code in my App Delegate:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (__persistentStoreCoordinator != nil)
{
return __persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"ISDEmployees.sqlite"];
NSLog(#"storeURL:%#",storeURL);
NSError *error = nil;
// Create a dictionary for automatic lightweight core data migration
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
// Set up the persistent store and migrate if needed
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __persistentStoreCoordinator;
}
Basically the standard persistentStoreCoordinator with the addition of the migration options. This code works great and my database is successfully updated. The problem that I'm having is that after the database update, I need to refresh all of the data in the database so that the new columns are populated. I was thinking that I would delete the data from the relevant entities/tables and force the application to re-download a new dataset with the added columns/attributes.
I'm not sure how/where to perform the delete/updates. The general application flow is this:
Log in with validation against an web API
On successful login, call the API and get latest added/updated records.
Display the updated data
I know I can check to see if a migration is needed by adding this code to persistentStoreCoordinator:
// Get the current data store meta data
BOOL migrationNeeded = NO;
NSDictionary *existingStoreData = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:storeURL error:&error];
if (existingStoreData)
{
// Check to see if new model is not the same as the existing mode, meaning a migration is required
if (![self.managedObjectModel isConfiguration:nil compatibleWithStoreMetadata:existingStoreData])
{
migrationNeeded = YES;
}
}
Any help would be greatly appreciated!!
Update #1:
Based on the feedback below, I've made the following changes:
Changed the migrationNeeded from a local to a public class variable on the AppDelegate.
On the Login View, I've added the following method:
- (void)checkForDatabaseMigration
{
// Get a copy of the managed object context. If a migration is needed, it will kick it off
NSManagedObjectContext *managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
if ([(AppDelegate *)[[UIApplication sharedApplication] delegate] migrationNeeded])
{
// Delete all data from the table
}
managedObjectContext = nil;
}
Does that seem right? The code works and the data is removed after migration and a fresh copy is inserted. I just hate to check for migration each time the application starts.
If you know how to determine when to delete old data, all you need is to fetch all the enteties you need and delete them. Here is how you do that(for example, if you want to delete all Man enteties):
NSFetchRequest * request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Man" inManagedObjectContext:myContext]];
[request setIncludesPropertyValues:NO]; //only fetch the managedObjectID
NSError * error = nil;
NSArray * men = [myContext executeFetchRequest:request error:&error];
//error handling goes here
for (NSManagedObject * man in men) {
[myContext deleteObject:man];
}
NSError *saveError = nil;
[myContext save:&saveError];
//more error handling here
I have a populated sqlite database in my app reousrce-folder. On startup I want to preload coredata-store with the data of this sqlite db. I use the NSMigrationManager in the persistantStoreCoordinator method. This works great at the first time and will append the data to the store. But it will append the data on each startup again, so the data will be duplicated, after the second startup. How can I solve this? In a database I would use primary keys, is there something similar in the data model? Or can I compare the entity-objects?
Thanks four your help, below the method I use:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator_ != nil) {
return persistentStoreCoordinator_;
}
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:#"Raetselspass.sqlite"];
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"Raetselspass" ofType:#"sqlite"];
NSURL *defaultStoreUrl = [NSURL fileURLWithPath:defaultStorePath];
/*
Set up the store.
For the sake of illustration, provide a pre-populated default store.
*/
// CANNOT USE THIS BELOW: WILL WORK ONCE, BUT WHEN I WILL UPDATE THE APP WITH
// NEW DATA TO APPEND, THEN THIS WILL NOT WORK
// NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn’t exist, copy the default store.
// if (![fileManager fileExistsAtPath:storePath]) {
// if (defaultStorePath) {
// [fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
// }
// }
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]);
abort(); // Fail
}
//migration
rror:&error];
NSError *err = nil;
NSMigrationManager *migrator = [[NSMigrationManager alloc] initWithSourceModel:[self managedObjectModel] destinationModel:[self managedObjectModel]];
NSMappingModel *mappingModel = [NSMappingModel inferredMappingModelForSourceModel:[self managedObjectModel] destinationModel:[self managedObjectModel] error:&err];
NSError *err2;
if (![migrator migrateStoreFromURL:defaultStoreUrl
type:NSSQLiteStoreType
options:nil
withMappingModel:mappingModel
toDestinationURL:storeUrl
destinationType:NSSQLiteStoreType
destinationOptions:nil
error:&err2])
{
//handle the error
}
NSLog(#"import finished");
[migrator release];
return persistentStoreCoordinator_;
}
The code as provided will merge the default file if it is present in the documents folder. If you delete the file after you merge it, then it shouldn't load every time. You could set a user default flag to record if it had been previously merged.
A better solution would be to create a Core Data persistent store with the default data and include that in the app bundle. Upon first launch, copy that file to the documents folder and just assign it to the persistent store. That method would be faster and you wouldn't have to worry about the merge failing.