Core data custom migration - ios

My old core data model has an NSDate field, which I would like to change to a NSNumber. I read the Apple documentation and several similar questions on SO and other blogs (see references at end of question)
But no matter what I do, I keep getting the same error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Mismatch between mapping and source/destination models'
I only have 2 versions of the model, and I have verified time and again that the source and destination models are correct.
I even discarded all my changes and recreated a new model, mappings and entities (NSManagedObject subclasses). I've been stuck on this for almost 2 days now, and have no clue anymore as to what I'm doing. Any pointers on what I'm doing wrong will be greatly appreciated.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"Old.sqlite"];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSString *sourceStoreType = NSSQLiteStoreType;
NSURL *sourceStoreURL = storeURL;
NSURL *destinationStoreURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"New.sqlite"];
NSString *destinationStoreType = NSSQLiteStoreType;
NSDictionary *destinationStoreOptions = nil;
NSDictionary *sourceMetadata =
[NSPersistentStoreCoordinator metadataForPersistentStoreOfType:sourceStoreType
URL:sourceStoreURL
error:&error];
if (sourceMetadata == nil) {
NSLog(#"source metadata is nil");
}
NSManagedObjectModel *destinationModel = [_persistentStoreCoordinator managedObjectModel];
BOOL pscCompatibile = [destinationModel
isConfiguration:nil
compatibleWithStoreMetadata:sourceMetadata];
if (pscCompatibile) {
// no need to migrate
NSLog(#"is compatible");
} else {
NSLog(#"is not compatible");
NSManagedObjectModel *sourceModel =
[NSManagedObjectModel mergedModelFromBundles:nil
forStoreMetadata:sourceMetadata];
if (sourceModel != nil) {
NSLog(#"source model is not nil");
NSMigrationManager *migrationManager =
[[NSMigrationManager alloc] initWithSourceModel:sourceModel
destinationModel:destinationModel];
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:#"MyMigrationMapping" withExtension:#"cdm"];
NSMappingModel *mappingModel = [[NSMappingModel alloc] initWithContentsOfURL:fileURL];
NSArray *newEntityMappings = [NSArray arrayWithArray:mappingModel.entityMappings];
for (NSEntityMapping *entityMapping in newEntityMappings) {
entityMapping.entityMigrationPolicyClassName = NSStringFromClass([ConvertDateToNumberTransformationPolicy class]);
}
mappingModel.entityMappings = newEntityMappings;
BOOL ok = [migrationManager migrateStoreFromURL:sourceStoreURL
type:sourceStoreType
options:nil
withMappingModel:mappingModel
toDestinationURL:destinationStoreURL
destinationType:destinationStoreType
destinationOptions:nil
error:&error];
if (ok) {
storeURL = destinationStoreURL;
}
} else {
NSLog(#"e nil source model");
}
}
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
My custom NSEntityMigration class:
- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance
entityMapping:(NSEntityMapping *)mapping
manager:(NSMigrationManager *)manager
error:(NSError **)error
{
// Create a new object for the model context
NSManagedObject *newObject =
[NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName]
inManagedObjectContext:[manager destinationContext]];
NSArray *arrayOfKeys = #[#"startDate", #"endDate", #"creationTime", #"timeStamp"];
for (NSString *key in arrayOfKeys) {
// do our transfer of NSDate to NSNumber
NSDate *date = [sInstance valueForKey:key];
NSLog(#"Key: %#, value: %#", key, [date description]);
// set the value for our new object
[newObject setValue:[NSNumber numberWithDouble:[date timeIntervalSince1970]] forKey:key];
}
// do the coupling of old and new
[manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];
return YES;
}
Some references:
Example or explanation of Core Data Migration with multiple passes?
Core Data - Default Migration ( Manual )
http://www.preenandprune.com/cocoamondo/?p=468
http://www.timisted.net/blog/archive/core-data-migration/

I admit I don't understand the cause of the error. In my migration I have one policy per entity and I am checking for the entity before using it. Not sure if this extra if will help you:
- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance
entityMapping:(NSEntityMapping *)mapping
manager:(NSMigrationManager *)manager
error:(NSError **)error {
NSEntityDescription *sourceInstanceEntity = [sInstance entity];
if ([[sInstance name] isEqualToString:#"<-name-of-entity>"] ) {
newObject = [NSEntityDescription insertNewObjectForEntityForName:#"<-name-of-entity>"
inManagedObjectContext:[manager destinationContext]];
NSArray *arrayOfKeys = #[#"startDate", #"endDate", #"creationTime", #"timeStamp"];
for (NSString *key in arrayOfKeys) {
// do our transfer of NSDate to NSNumber
NSDate *date = [sInstance valueForKey:key];
NSLog(#"Key: %#, value: %#", key, [date description]);
// set the value for our new object
[newObject setValue:[NSNumber numberWithDouble:[date timeIntervalSince1970]] forKey:key];
}
}
// do the coupling of old and new
[manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];
return YES;
}

Everything you are doing is way more complicated than it has to be. You can do all of this without migrating you database at all. You can add another property to your subclass that implements it:
///in your .h
#property(nonatomic, copy) NSNumber* startDateNumber
/// in you .m
-(NSNumber*) startDateNumber{
if (self.startDate) {
return #(self.startDate.timeIntervalSince1970);
}
return nil;
}
-(void)setStartDateNumber:(NSNumber*)startDateNumber{
if(startDateNumber){
self.startDate =[NSDate dateWithTimeIntervalSince1970:startDateNumber.doubleValue];
}else{
self.startDate = nil;
}
}
It is a little annoying to have duplicate properties (startDate and startDateNumber) but it is so much simpler and doesn't have any of the migration issues.

Related

Inconsistency in Saving/Fetching Data in Core Data

My first post here, getting straight to the point.
I'm facing a problem and I'm not sure whether is with saving of data or fetching it.
Here are my codes where I tried to save the data in my Entity, "Sequence". The entity consists of 2 attributes "seqForWk1CD" & "seqForWk2CD".
AppDelegate *delegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [delegate managedObjectContext];
SequenceMO *seqEntity = [NSEntityDescription
insertNewObjectForEntityForName:#"Sequence"
inManagedObjectContext:context];
// some other code
seqEntity.seqForWk1CD = arrForWk1;
seqEntity.seqForWk2CD = arrForWk2;
NSLog(#"%#", arrForWk1);
NSLog(#"%#", arrForWk2);
NSError *error = nil;
if (context != nil) {
if ([context hasChanges] && ![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
When printed, the arrays will always display the contents of the array.
This is where I try to fetch the data.
AppDelegate *delegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [delegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Sequence" inManagedObjectContext:context];
[request setEntity:entity];
NSError *error;
if(![context save:&error]){
NSLog(#"Error fetching. %# %#", error, [error localizedDescription]);
}
NSUInteger count = [context countForFetchRequest:request error:&error];
if(count != 0){
NSArray *fetchObj = [context executeFetchRequest:request error:&error];
NSManagedObject *sequence = (NSManagedObject *)[fetchObj objectAtIndex:0];
NSLog(#"1 - %#", sequence);
arrForWk1 = [sequence valueForKey:#"seqForWk1CD"];
NSLog(#"%#", arrForWk1);
arrForWk2 = [sequence valueForKey:#"seqForWk2CD"];
NSLog(#"%#", arrForWk2);
}
The problem comes when I restart the application. The arrays either show (null) for both arrays or it shows the contents of both of the arrays. The if statement for if(![context save:&error]) never gets triggered. Subclasses of NSManagedObject for the entity has already been added.I've also tried declaring the AppDelegate in #interface and forced to save the context immediately by doing [delegate saveContext];.Here is the method where the saving happens. "check" is initialized to 0 at the viewDidLoad method. Both "arrForWk1" & "arrForWk2" are declared at #interface.
- (IBAction)randomizeSequence:(UIButton *)sender {
NSMutableArray *storeArray = [[NSMutableArray alloc] init];
AppDelegate *delegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [delegate managedObjectContext];
SequenceMO *seqEntity = [NSEntityDescription insertNewObjectForEntityForName:#"Sequence" inManagedObjectContext:context];
BOOL record = NO;
int x;
for (int i=0; [storeArray count] < 9; i++) //Loop for generate different random values
{
x = 1 + arc4random() % 9;//generating random number
if(i==0)//for first time
{
[storeArray addObject:[NSNumber numberWithInt:x]];
}
else
{
for (int j=0; j<= [storeArray count]-1; j++)
{
if (x ==[[storeArray objectAtIndex:j] intValue])
record = YES;
}
if (record == YES)
{
record = NO;
}
else
{
[storeArray addObject:[NSNumber numberWithInt:x]];
}
}
}
check++;
if(check == 1 ) {
arrForWk1 = storeArray;
[self.wk1Seq reloadData];
}
else if(check == 2) {
arrForWk2 = storeArray;
seqEntity.seqForWk1CD = arrForWk1;
seqEntity.seqForWk2CD = arrForWk2;
NSError *error = nil;
if (context != nil) {
if ([context hasChanges] && ![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
[self.wk2Seq reloadData];
}
Just to add on, the arrays are of data type NSMutableArray and I'm trying to store them into attributes of type "Transformable".
After researching around I managed to solve the problem. I'm not sure how it actually works but I solved it by rearranging the code when I'm trying to fetch the data.
AppDelegate *delegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [delegate managedObjectContext];
NSEntityDescription *descriptor = [NSEntityDescription entityForName:#"Sequence" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
request.entity = descriptor;
NSError *error;
NSArray *fetchObj = [context executeFetchRequest:request error:&error];
if(fetchObj == nil) {
NSLog(#"Error occured when trying to fetch.");
}
else {
if(fetchObj.count == 0) {
NSLog(#"No objects saved");
else {
NSManagedObject *sequence = (NSManagedObject *)[fetchObj objectAtIndex:0];
NSLog(#"1 - %#", sequence);
arrForWk1 = [sequence valueForKey:#"seqForWk1CD"];
NSLog(#"%#", arrForWk1);
arrForWk2 = [sequence valueForKey:#"seqForWk2CD"];
NSLog(#"%#", arrForWk2);
NSLog(#"2 - %#", sequence);
}
I've tried also tried 2 ways of saving the data. In the if statement where I tried to save the data, I converted the NSMutableArrays to NSArrays.
else if(check == 2) {
test2 = storeArray;
NSManagedObjectContext *context = [delegate managedObjectContext];
SequenceMO *seqEntity = [NSEntityDescription insertNewObjectForEntityForName:#"Sequence" inManagedObjectContext:context];
NSArray *tArr = [arrForWk1 copy];
NSArray *tArr2 = [arrForWk2 copy];
seqEntity.seqForWk1CD = tArr;
seqEntity.seqForWk2CD = tArr2;
NSError *error = nil;
if (context != nil) {
if ([context hasChanges] && ![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
The second way I tried is by using an answer from Core Data not saving changes to Transformable property
id temp = [seqEntity seqForWk1CD];
id temp2 = [seqEntity seqForWk2CD];
temp = arrForWk1;
temp2 = arrForWk2;
[seqEntity setSeqForWk1CD:temp];
[seqEntity setSeqForWk2CD:temp2];
Apparently it worked somehow.

NSEntityMigrationPolicy creates new object instead of change existing one

I use NSEntityMigrationPolicy and createDestinationInstancesForSourceInstance method to change one attribute type from Data to String. However it creates new objects instead of replacing the existing one.
Here is my code:
#import "MessageTransformationPolicy.h"
#implementation MessageTransformationPolicy
- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance
entityMapping:(NSEntityMapping *)mapping
manager:(NSMigrationManager *)manager
error:(NSError **)error
{
// Create a new object for the model context
NSManagedObject *newObject =
[NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName]
inManagedObjectContext:[manager destinationContext]];
// do our transfer of nsdate to nsstring
NSData *messageMetadata_dataType = [sInstance valueForKey:#"metadata"];
NSString *messageMetadata_stringType = [[NSString alloc] initWithData:messageMetadata_dataType encoding:NSUTF8StringEncoding];
// set the value for our new object
[newObject setValue:messageMetadata_stringType forKey:#"metadata"];
// do the coupling of old and new
[manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];
return YES;
}
#end
Where is the problem?
By changing the way of storing and saving all attributes of the target entity named Msg, the problem solved:
- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)inSourceInstance
entityMapping:(NSEntityMapping *)inMapping
manager:(NSMigrationManager *)inManager
error:(NSError **)outError {
NSManagedObject *newObject;
NSEntityDescription *sourceInstanceEntity = [inSourceInstance entity];
// sure to have the right entity
if ( [[sourceInstanceEntity name] isEqualToString:#"Msg"] ) {
newObject = [NSEntityDescription insertNewObjectForEntityForName:#"Msg" inManagedObjectContext:[inManager destinationContext]];
// get the attributes
NSDictionary *keyValDict = [inSourceInstance committedValuesForKeys:nil];
NSArray *allKeys = [[[inSourceInstance entity] attributesByName] allKeys];
// loop over the attributes
for (NSString *key in allKeys) {
// Get key and value
id value = [keyValDict objectForKey:key];
if ( [key isEqualToString:#"metadata"] ) {
NSData *messageMetadata_dataType = [keyValDict valueForKey:#"metadata"];
NSString *messageMetadata_stringType = [[NSString alloc] initWithData:messageMetadata_dataType encoding:NSUTF8StringEncoding];
[newObject setValue:messageMetadata_stringType forKey:key];
} else {
[newObject setValue:value forKey:key];
}
}
[inManager associateSourceInstance:inSourceInstance withDestinationInstance:newObject forEntityMapping:inMapping];
}
return YES;
}

RestKit: 'NSInternalInconsistencyException', reason: 'Unable to perform mapping: No `managedObjectContext` assigned

I'm getting the following error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Unable to perform mapping: No `managedObjectContext` assigned. (Mapping response.URL = https://www.someurl.com/lastrequest=2014-12-08T02%3A44%3A52Z)'
The app stops at the following line in RKResponseMapperOperation.m:
- (RKMappingResult *)performMappingWithObject:(id)sourceObject error:(NSError **)error
{
NSLog(#"managedObjectContext: %#, Source Object: %# Error: %#", self.managedObjectContext, sourceObject, (*error).description);
NSAssert(self.managedObjectContext, #"Unable to perform mapping: No `managedObjectContext` assigned. (Mapping response.URL = %#)", self.response.URL);
....
I noticed that the above method was called 27 (this number varies) times prior to the app crashing. In each instance, NSManagedObjectContext was present i.e. the line below:
2014-12-07 18:44:48.721 MyApp[19011:3258405] managedObjectContext:managedObjectContext: <NSManagedObjectContext: 0x1701f5800>, Source Object: {
friends = (
);
} Error: (null)
However right before it crashed, the NSManagedObjectContext was null:
2014-12-07 18:44:53.454 MyApp[19011:3258404] managedObjectContext: (null), Source Object: {
friends = (
);
} Error: (null)
Since the app functions normally for a while before it crashes, I'm not sure how to address this. Any pointers would be greatly appreciated.
* EDIT *
In Appdelegaate. This method is called once in viewDidLoad when the User logs in.
- (RKManagedObjectStore *)managedObjectStore
{
if (!_managedObjectStore && [Persistence loggedIn])
{
NSError * error;
NSURL * modelURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"App" ofType:#"momd"]];
NSManagedObjectModel * managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] mutableCopy];
self.managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
[_managedObjectStore createPersistentStoreCoordinator];
NSArray * searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * documentPath = [searchPaths objectAtIndex:0];
NSString *dbName = [NSString stringWithFormat:#"%#App%#.sqlite", documentPath, [Persistence username]];
NSPersistentStore * persistentStore = [_managedObjectStore addSQLitePersistentStoreAtPath:dbName
fromSeedDatabaseAtPath:nil
withConfiguration:nil
options:[self optionsForSqliteStore]
error:&error];
NSAssert(persistentStore, #"Failed to add persistent store with error: %#", error);
NSLog(#"Path: %#", dbName);
if(!persistentStore)
{
NSLog(#"Failed to add persistent store: %#", error);
}
[_managedObjectStore createManagedObjectContexts];
self.managedObjectStore.managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:self.managedObjectStore.persistentStoreManagedObjectContext];
return self.managedObjectStore;
}
return _managedObjectStore;
}
- (id)optionsForSqliteStore
{
return #{
NSInferMappingModelAutomaticallyOption: #YES,
NSMigratePersistentStoresAutomaticallyOption: #YES
};
}
Creating MOC:
For Core Data stack, I'm using the Default Core Data code in AppDelegate that's provided when the project is created in Xcode.
- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
MOC Operation:
- (void)saveContext
{
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
}
Inside the App, the methods below are used to set, get, and clear ObjectManager:
- (void)refreshMOC
{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
self.objectManager = [self getObjectManager];
self.objectManager.managedObjectStore = appDelegate.managedObjectStore;
self.objectManager.managedObjectStore.managedObjectCache = appDelegate.managedObjectStore.managedObjectCache;
self.managedObjectContext = self.objectManager.managedObjectStore.mainQueueManagedObjectContext;
}
- (RKObjectManager *)setupObjectManager
{
NSURL *baseURL = [NSURL URLWithString:kBaseURL];
AFHTTPClient *httpClient = [[AFHTTPClient alloc]initWithBaseURL:baseURL];
RKObjectManager *manager = [[RKObjectManager alloc]initWithHTTPClient:httpClient];
[manager.HTTPClient registerHTTPOperationClass:[AFJSONRequestOperation class]];
[manager setAcceptHeaderWithMIMEType:RKMIMETypeJSON];
[manager.HTTPClient setParameterEncoding:AFJSONParameterEncoding];
[RKMIMETypeSerialization registeredMIMETypes];
[RKObjectManager setSharedManager:manager];
return [RKObjectManager sharedManager];
}
- (RKObjectManager *)getObjectManager
{
self.objectManager = (!self.objectManager) ? [self setupObjectManager] : self.objectManager;
return self.objectManager;
}
- (RKObjectManager*)newObjectManager
{
[self clearRKObjectManager];
return [self getObjectManager];
}
- (void)clearRKObjectManager
{
if (self.objectManager)
{
self.objectManager = nil;
}
}
Remove all of the app delegate template Core Data methods. When you use RestKit and create a managed object store you're asking RestKit to manage the Core Data stack for you so those other methods are not required (and confuse things).
When you need a MOC, get it / one from the managed object store.
Note, the above applies to saving too as you need to use the RestKit method for saving to the persistent store rather than just saving the individual MOC.

Core Data NSPersistentStoreCoordinator's +metadataForPersistentStoreOfType:URL:error: sometimes returns nil

I have a method that progressively migrates a core data sqlite store though multiple NSManagedObjectModel versions until the store is at a the current version. The method is inspired by the code in Marcus Zarra's Core Data book.
The app is in production but my method is failing in about 0.5% of cases. When it fails it returns NO and an error is logged using Crashlytics:
NSSQLiteErrorDomain = 14; NSUnderlyingException = "I/O error for database at <store url>. SQLite error code:14, 'unable to open database file'"
The sqlite store use's write-ahead logging (WAL) and I am able to sometimes get the same error if I call +metadataForPersistentStoreOfType:URL:error: after first manually deleting the sqlite-WAL file.
Progressive Migration Code:
- (BOOL)progressivelyMigrateURL:(NSURL*)sourceStoreURL
toModel:(NSManagedObjectModel*)finalModel
error:(NSError**)error
{
NSURL *storeDirectoryURL = [sourceStoreURL URLByDeletingLastPathComponent];
NSString *storeExtension = [sourceStoreURL pathExtension];
NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator
metadataForPersistentStoreOfType:NSSQLiteStoreType
URL:sourceStoreURL
error:error];
if (!sourceMetadata) return NO;
while (![finalModel isConfiguration:nil
compatibleWithStoreMetadata:sourceMetadata]) {
NSManagedObjectModel *sourceModel =
[self managedObjectModelForMetadata:sourceMetadata];
if (!sourceModel) return NO;
NSString *modelName = nil;
NSManagedObjectModel *targetModel =
[self suitableTargetModelForMigrationFromSourceModel:sourceModel
modelName:&modelName];
if (!targetModel) return NO;
NSMigrationManager *manager =
[[NSMigrationManager alloc] initWithSourceModel:sourceModel
destinationModel:targetModel];
NSMappingModel *mappingModel =
[NSMappingModel mappingModelFromBundles:nil
forSourceModel:sourceModel
destinationModel:targetModel];
NSURL *destinationStoreURL =
[[storeDirectoryURL URLByAppendingPathComponent:modelName]
URLByAppendingPathExtension:storeExtension];
BOOL migrated = [manager migrateStoreFromURL:sourceStoreURL
type:NSSQLiteStoreType
options:nil
withMappingModel:mappingModel
toDestinationURL:destinationStoreURL
destinationType:NSSQLiteStoreType
destinationOptions:nil
error:error];
if (!migrated) return NO;
NSString *sourceModelName =
[self versionStringForManagedObjectModel:sourceModel];
NSURL *backUpURL = [self backupURLWithDirectoryURL:storeDirectoryURL
pathExtension:storeExtension
modelName:sourceModelName];
BOOL replaced = [self replaceStoreAtURL:sourceStoreURL
withStoreAtURL:destinationStoreURL
backupURL:backUpURL
error:error];
if (replaced == NO) return NO;
sourceMetadata = [NSPersistentStoreCoordinator
metadataForPersistentStoreOfType:NSSQLiteStoreType
URL:sourceStoreURL
error:error];
if (!sourceMetadata) return NO;
}
return YES;
}
- (NSManagedObjectModel *)managedObjectModelForMetadata:(NSDictionary *)metadata
{
for (NSURL *URL in [self modelURLs]) {
NSManagedObjectModel *model =
[[NSManagedObjectModel alloc] initWithContentsOfURL:URL];
if ([model isConfiguration:nil compatibleWithStoreMetadata:metadata]) {
return model;
}
}
return nil;
}
- (NSManagedObjectModel *)suitableTargetModelForMigrationFromSourceModel:(NSManagedObjectModel *)sourceModel
modelName:(NSString **)modelName
{
for (NSURL *modelURL in [self modelURLs]) {
NSManagedObjectModel *targetModel =
[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSMappingModel *mappingModel =
[NSMappingModel mappingModelFromBundles:nil
forSourceModel:sourceModel
destinationModel:targetModel];
if (mappingModel) {
*modelName = [[modelURL lastPathComponent] stringByDeletingPathExtension];
return targetModel;
}
}
return nil;
}
- (NSArray *)modelURLs
{
NSMutableArray *modelURLs =
[[[NSBundle mainBundle] URLsForResourcesWithExtension:#"mom"
subdirectory:nil] mutableCopy];
NSArray *momdURLs =
[[[NSBundle mainBundle] URLsForResourcesWithExtension:#"momd"
subdirectory:nil] mutableCopy];
for (NSURL *momdURL in momdURLs) {
NSString *directory = [momdURL lastPathComponent];
NSArray *array =
[[NSBundle mainBundle] URLsForResourcesWithExtension:#"mom"
subdirectory:directory];
[modelURLs addObjectsFromArray:array];
}
return [modelURLs copy];
}
- (NSURL *)backupURLWithDirectoryURL:(NSURL *)URL
pathExtension:(NSString *)extension
modelName:(NSString *)name
{
NSString *GUID = [[NSProcessInfo processInfo] globallyUniqueString];
NSString *pathComponant = [NSString stringWithFormat:#"%#-%#", GUID, name];
return [[URL URLByAppendingPathComponent:pathComponant]
URLByAppendingPathExtension:extension];
}
- (BOOL)replaceStoreAtURL:(NSURL *)originalStoreURL
withStoreAtURL:(NSURL *)newStoreURL
backupURL:(NSURL *)backupURL
error:(NSError **)error
{
BOOL storeMoved = [self moveStoreAtURL:originalStoreURL
toURL:backupURL
error:error];
if (!storeMoved) return NO;
storeMoved = [self moveStoreAtURL:newStoreURL
toURL:originalStoreURL
error:error];
if (!storeMoved) return NO;
return YES;
}
- (BOOL)moveStoreAtURL:(NSURL *)sourceURL
toURL:(NSURL *)targetURL
error:(NSError **)error
{
NSMutableArray *sourceURLs = [#[sourceURL] mutableCopy];
NSMutableArray *targetURLs = [#[targetURL] mutableCopy];
NSString *walExtension = #"sqlite-wal";
if ([self storeAtURL:sourceURL hasAccessoryFileWithExtension:walExtension]) {
[sourceURLs addObject:[self URLByReplacingExtensionOfURL:sourceURL
withExtension:walExtension]];
[targetURLs addObject:[self URLByReplacingExtensionOfURL:targetURL
withExtension:walExtension]];
}
NSString *shmExtension = #"sqlite-shm";
if ([self storeAtURL:sourceURL hasAccessoryFileWithExtension:shmExtension]) {
[sourceURLs addObject:[self URLByReplacingExtensionOfURL:sourceURL
withExtension:shmExtension]];
[targetURLs addObject:[self URLByReplacingExtensionOfURL:targetURL
withExtension:shmExtension]];
}
NSFileManager *fileManager = [NSFileManager defaultManager];
for (int i = 0; i < [sourceURLs count]; i++) {
BOOL fileMoved = [fileManager moveItemAtURL:sourceURLs[i]
toURL:targetURLs[i]
error:error];
if (!fileMoved) return NO;
}
return YES;
}
- (BOOL)storeAtURL:(NSURL *)URL hasAccessoryFileWithExtension:(NSString *)extension
{
NSURL *accessoryURL = [self URLByReplacingExtensionOfURL:URL
withExtension:extension];
return [[NSFileManager defaultManager] fileExistsAtPath:[accessoryURL path]];
}
- (NSURL *)URLByReplacingExtensionOfURL:(NSURL *)URL withExtension:(NSString *)extension
{
return [[URL URLByDeletingPathExtension] URLByAppendingPathExtension:extension];
}
- (NSString *)versionStringForManagedObjectModel:(NSManagedObjectModel *)model
{
NSString *string = #"";
for (NSString *identifier in model.versionIdentifiers) {
string = [string stringByAppendingString:identifier];
}
return string;
}
Sorry for the super long code.
The likely cause is your moveStoreAtURL:toURL:error: method. The error you're getting is mentioned in Apple's docs as being the result of failing to copy all of a persistent store's files. It looks like you're trying to hit all of them, but either (a) there's a bug in the copy code that I can't find right now or (b) the store is "live" in your app, being used by a persistent store coordinator, and so you're not getting a consistent state from the copy.
You might be able to fix it with some debugging, and if you ensured that the store was not in use. It would be better and probably more reliable to change the journal mode so that you don't have wal and shm files (which that link also describes). Even better, if your store files aren't too huge, use migratePersistentStore:toURL:options:withType:error to have Core Data make the copy. That should be pretty much guaranteed to work, though in some cases it can use too much memory.
I'm using NSMigrationManager, so I can't use NSPersistentStoreCoordinator's - migratePersistentStore..., so my solution was to force a checkpoint operation:
- (void)performCheckpointStoreWithSourceModel:(NSManagedObjectModel *)sourceModel sourceStoreURL:(NSURL *)sourceStoreURL {
NSPersistentStoreCoordinator *tempPSC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:sourceModel];
[tempPSC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:sourceStoreURL options:#{NSSQLitePragmasOption: #{#"journal_mode": #"DELETE"}} error:nil];
[tempPSC removePersistentStore:[tempPSC persistentStoreForURL:sourceStoreURL] error:nil];
}
...before performing migration with NSMigrationManager:
if (![manager migrateStoreFromURL:sourceStoreURL
type:type
options:nil
withMappingModel:mappingModel
toDestinationURL:destinationStoreURL
destinationType:type
destinationOptions:nil
error:error]) {
return NO;
}

Writing to coredata, even when done on background thread , blocks the UI considerably

Iam trying out a chat based app. I have setup a singleton coredata manager as follows
#import "CGSharedCoreData.h"
CGSharedCoreData *_cd;
#implementation CGSharedCoreData
#synthesize managedObjectModel = managedObjectModel_;
#synthesize managedObjectContext = managedObjectContext_;
#synthesize persistentStoreCoordinator = persistentStoreCoordinator_;
+ (CGSharedCoreData *)sharedCoreData{
static CGSharedCoreData *_cd = nil;
static dispatch_once_t onceCoreDataShared;
dispatch_once(&onceCoreDataShared, ^{
_cd = [[CGSharedCoreData alloc] init];
});
return _cd;
}
+ (void)saveContext:(NSManagedObjectContext*)c{
#try {
if (c.persistentStoreCoordinator.persistentStores.count == 0)
{
// This is the case where the persistent store is cleared during a logout.
CGLog(#"saveContext: PersistentStoreCoordinator is deallocated.");
return;
}
// Register context with the notification center
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
CGSharedCoreData *sharedCoreData = [CGSharedCoreData sharedCoreData];
[nc addObserver:sharedCoreData
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:c];
NSError *error = nil;
if (c != nil) {
CGLog(#"thread &&*&(*(* %d",[NSThread isMainThread]);
if ([c hasChanges] && ![c save:&error]) {
CGLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
[nc removeObserver:sharedCoreData name:NSManagedObjectContextDidSaveNotification
object:c];
}
#catch (NSException *exception) {
CGLog(#"***** Unresolved CoreData exception %#", [exception description]);
}
}
+ (dispatch_queue_t) backgroundSaveQueue
{
static dispatch_queue_t coredata_background_save_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
coredata_background_save_queue = dispatch_queue_create("com.shashank.coredata.backgroundsaves", NULL);
});
return coredata_background_save_queue;
}
+ (void)performInTheBackground:(void (^)(NSManagedObjectContext *blockContext))bgBlock {
dispatch_async([CGSharedCoreData backgroundSaveQueue],
^{
NSManagedObjectContext *newContext = [[NSManagedObjectContext alloc] init];
[newContext setPersistentStoreCoordinator:CG_CORE_DATA.persistentStoreCoordinator]; // Create a managed object context
[newContext setStalenessInterval:0.0];
[newContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
bgBlock(newContext);
});
}
- (void)saveContext {
[CGSharedCoreData saveContext:managedObjectContext_];
}
- (void)clearStore {
NSError *error = nil;
if (self.persistentStoreCoordinator) {
if ([persistentStoreCoordinator_ persistentStores] == nil) {
CGLog(#"No persistent stores to clear!");
}
else {
CGLog(#"Cleaning persistent stores!");
managedObjectContext_ = nil;
NSPersistentStore *store = [[persistentStoreCoordinator_ persistentStores] lastObject];
if (![persistentStoreCoordinator_ removePersistentStore:store error:&error]) {
CGLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
// Delete file
if ([[NSFileManager defaultManager] fileExistsAtPath:store.URL.path]) {
if (![[NSFileManager defaultManager] removeItemAtPath:store.URL.path error:&error]) {
CGLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
// Delete the reference to non-existing store
persistentStoreCoordinator_ = nil;
}
}
}
#pragma mark - Core Data stack
// Returns the managed object context for the application.
// If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
- (NSManagedObjectContext *)managedObjectContext
{
NSAssert([NSThread isMainThread], #"Must be instantiated on main thread.");
if (managedObjectContext_ != nil) {
return managedObjectContext_;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil)
{
managedObjectContext_ = [[NSManagedObjectContext alloc] init];
[managedObjectContext_ setPersistentStoreCoordinator:coordinator];
[managedObjectContext_ setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
}
return managedObjectContext_;
}
// Returns the managed object model for the application.
// If the model doesn't already exist, it is created from the application's model.
- (NSManagedObjectModel *)managedObjectModel
{
if (managedObjectModel_ != nil) {
return managedObjectModel_;
}
managedObjectModel_ = [NSManagedObjectModel mergedModelFromBundles:nil];
return managedObjectModel_;
}
// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator_ != nil) {
return persistentStoreCoordinator_;
}
CGLog(#"Creating a persistent store!");
NSURL *storeURL = [NSURL fileURLWithPath: [[self applicationLibraryDirectory] stringByAppendingPathComponent: #"CGChatData.sqlite"]];
NSError *error = nil;
persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
CGLog(#"Unresolved error %#, %#", error, [error userInfo]);
// abort();
CGLog(#"Delete STORE: %d",[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]);
persistentStoreCoordinator_ = nil;
persistentStoreCoordinator_ = self.persistentStoreCoordinator;
}
return persistentStoreCoordinator_;
}
#pragma mark -
#pragma mark Handling Multiple Contexts
/**
Merges the changes from the insert contexts to the main context on the main thread.
*/
- (void)mergeChanges:(NSNotification *)notification
{
// Merge changes into the main context on the main thread
if(![[CGLoginEngine sharedLoginEngine] isLoggedIn])
return;
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:#selector(mergeChanges:)
withObject:notification waitUntilDone:YES];
return;
}
//CAUTION: Without the for clause below the NSFetchedResultsController may not capture all the changed objects.
//For more info see: http://stackoverflow.com/questions/3923826/nsfetchedresultscontroller-with-predicate-ignores-changes-merged-from-different
//and http://stackoverflow.com/questions/2590190/appending-data-to-nsfetchedresultscontroller-during-find-or-create-loop
for (NSManagedObject *object in [[notification userInfo] objectForKey:NSUpdatedObjectsKey])
{
[[managedObjectContext_ objectWithID:[object objectID]] willAccessValueForKey:nil];
}
[managedObjectContext_ mergeChangesFromContextDidSaveNotification:notification];
}
now , whenever i need to send a message , I am writing it to core data and updating the tableview with a fetched results controller. The writing part looks like this :
[CGSharedCoreData performInTheBackground:^(NSManagedObjectContext *blockContext)
{
CGLog(#"thread ******* is main %d",[NSThread isMainThread]);
[[CGChatsModelController sharedChatModel] addChatWithtext:[chatMessage objectForKey:#"Message"]
username:[chatMessage objectForKey:#"Receiver"]
firstName:[chatMessage objectForKey:#"ReceiverFirstName"]
lastName:[chatMessage objectForKey:#"ReceiverLastName"]
imageId:[chatMessage objectForKey:#"ReceiverImageID"]
createdByMe:YES
time:time
context:blockContext];
[CGSharedCoreData saveContext:blockContext];
But , When I send multiple messages in a very short span , it entirely blocks my UI, even when the core-data saving and all other related operations are being done on a background queue.
Is there any particular reason for why ,this is happening ?
I am attaching a few other blocks of my code for reference here :
- (CGChat *) addChatWithtext:(NSString *)text username:(NSString *)username firstName:(NSString *)firstName lastName:(NSString *)lastName imageId:(NSString *)imageId createdByMe:(BOOL)yesOrNo time:(NSDate *)date context:(NSManagedObjectContext *)context
{
NSManagedObjectContext *backgroundContext = context;
CGChat *chat = (CGChat *)[NSEntityDescription insertNewObjectForEntityForName:#"CGChat" inManagedObjectContext:backgroundContext];
chat.text = text;
chat.createdByMe = [NSNumber numberWithBool:yesOrNo];
chat.status = #"sent";
[self addChat:chat toUserWithUserName:username firstName:firstName lastName:lastName imageID:imageId time:date WithContext:backgroundContext];
return chat;
}
- (CGChat *)lookUpChatWithUserName:(NSString *)username text:(NSString *)text timeStamp:(NSString *)timeStamp createdByMe:(BOOL) yesOrNo context:(NSManagedObjectContext *)context
{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"CGChat" inManagedObjectContext:context];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"userIchatWith == %# && text == %# && timeStamp == %# && createdByMe ==%#", [self lookUpForUserWithUsername:username inContext:context],text,timeStamp,[NSNumber numberWithBool:yesOrNo]];
[request setPredicate:predicate];
NSArray *resultArray = nil;
NSError *error;
resultArray = [context executeFetchRequest:request error:&error];
CGChat *chat = nil;
if ([resultArray count] > 0) {
chat = [resultArray objectAtIndex:0];
}
return chat;
}
- (void) addChat:(CGChat *)chat toUserWithUserName:(NSString *)username firstName:(NSString *)firstName lastName:(NSString *)lastName imageID:(NSString *)imageId time:(NSDate *)date WithContext:(NSManagedObjectContext *)context
{
CGUser *user = [self lookUpForUserWithUsername:username inContext:context];
if (!user)
{
user = (CGUser *)[NSEntityDescription insertNewObjectForEntityForName:#"CGUser" inManagedObjectContext:context];
user.userName = username ;
}
user.firstName = firstName;
user.lastName = lastName;
if (![user.imageID isEqualToString:imageId])
{
user.imageID = imageId;
}
CGChat *chats = [self getLastChatForUsername:username andContext:context];
if(chats)
{
chats.isLastChat = [NSNumber numberWithBool:NO];
}
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
chat.timeStamp = [dateFormat stringFromDate:date];
chat.isLastChat = [NSNumber numberWithBool:YES];
chat.userIchatWith = user;
user.timeOfLatestChat = [dateFormat stringFromDate:date];
}
- (CGChat *) getLastChatForUsername:(NSString *)username andContext:(NSManagedObjectContext *)context
{
CGUser *user = [self lookUpForUserWithUsername:username inContext:context];
if(user)
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"isLastChat == %#",[NSNumber numberWithBool:YES]];
NSSet *filteredSet = [user.chats filteredSetUsingPredicate:predicate];
return [[filteredSet allObjects] lastObject];
}
else
{
return nil;
}
}
- (CGUser *) lookUpForUserWithUsername:(NSString *)username inContext:(NSManagedObjectContext *)context
{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"CGUser" inManagedObjectContext:context];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"userName == %#", username];
[request setPredicate:predicate];
NSArray *resultArray = nil;
NSError *error;
resultArray = [context executeFetchRequest:request error:&error];
CGUser *user = nil;
if ([resultArray count] > 0) {
user = [resultArray objectAtIndex:0];
}
return user;
}
There are several issues here:
Core Data does not throw NSException, do not wrap your Core Data calls in try/catch. That will just create noise if something else does throw an exception. try/catch is VERY rarely used in Objective-C programming.
You are listening for and merging changes from NSManagedObjectContextDidSaveNotification calls when you are using a parent/child context design. This is incorrect. The parent/child relationship handles this for you, automatically, whenever you save the child context. When you listen for and consume that notification you are forcing Core Data to process that information a second time, on the main thread.
It is not clear what "background context" you are passing around as you do not show the code that calls your -add... methods. Background contexts should not persist for long periods of time. They are really meant to be used and destroyed. The longer you have a background context in existence the further from the main context it is going to be as changes in the main context do NOT get passed down to the child contexts.
Update
My apologies, I misread the code. Since you are merging the changes from a background thread into the main thread, there is no way to avoid blocking the main thread. This is one of the primary reasons for the creation of the parent/child design.
The only way to avoid this, and it is not recommended would be to:
Stand up a new NSPersistentStoreCoordinator pointed at the same sqlite file
Update that NSPersistentStoreCoordinator from the background thread
Notify the main NSManagedObjectContext to reload all data
This will avoid most of the main thread blocking but the expense in code complexity is almost always too much.

Resources