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.
Related
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.
I'm having a situation in which I have to deal with continuous fetch and save requests. My app is freezing when the amount of data I have to deal with increases. My savings are updated instantly to the tableview by NSFetchedResultsController. I tried to isolate my problem by using a sample code and I have put some code below. The freezing problem is in this region. I will have atleast 3000 records to save. Somebody please help me to tackle my UI freezing issue.
This is the profiler log on running the project : https://www.dropbox.com/s/tf1eiz3c5vnr0hq/Instruments10.trace.zip?dl=0
- (void)coreDataTest {
// Create NSManagedObjectModel and NSPersistentStoreCoordinator
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"Model" withExtension:#"momd"];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"store.sqlite"];
// remove old store if exists
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:[storeURL path]])
[fileManager removeItemAtURL:storeURL error:nil];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSPersistentStoreCoordinator *storeCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
[storeCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:nil
error:nil];
NSManagedObjectContext* masterContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterContext setPersistentStoreCoordinator:storeCoordinator];
// create the parent NSManagedObjectContext with the concurrency type to NSMainQueueConcurrencyType
_parentContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_parentContext setParentContext:masterContext];
// creat the child one with concurrency type NSPrivateQueueConcurrenyType
_childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_childContext setParentContext:_parentContext];
// create a NSEntityDescription for the only entity in this CoreData model: Test
NSEntityDescription *testDescription = [NSEntityDescription entityForName:#"Test"
inManagedObjectContext:_parentContext];
// perform a heavy write block on the child context
__block BOOL done = NO;
[_childContext performBlock:^{
for (int i = 0; i < 3000; i++){
Test *test = [[Test alloc] initWithEntity:testDescription
insertIntoManagedObjectContext:_childContext];
test.test = [NSString stringWithFormat:#"Test %d", i];
NSLog(#"Create test %d", i);
[_childContext save:nil];
done = YES;
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(#"Done write test: Saving parent");
[_parentContext save:nil];
NSFetchRequest *fr = [NSFetchRequest fetchRequestWithEntityName:#"Test"];
NSLog(#"Done: %d objects written", [[_parentContext executeFetchRequest:fr error:nil] count]);
[masterContext performBlock:^{
[masterContext save:nil];
// execute a fetch request on the parent to see the results
}];
});
}
}];
// execute a read request after 1 second
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSFetchRequest *fr = [NSFetchRequest fetchRequestWithEntityName:#"Test"];
[_parentContext performBlockAndWait:^{
NSLog(#"In between read: read %d objects", [[_parentContext executeFetchRequest:fr error:nil] count]);
}];
});
}
Try following steps that will improve speed
1 - Use NSFethedResultControler , it will help you to reload data automatically after every save , because its delegate is called after every save
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller{
[self fetchCoreDataArrayAfterSave];
}
2 - Try to save in batches (100) at a time
3 - User insertObject method of NSMangedObjectContext instead of save ,save will be use just after insertion of 100's batch .
for (int i = 0; i <first 100; i++){
Test *test = [[Test alloc] initWithEntity:testDescription
insertIntoManagedObjectContext:_childContext];
test.test = [NSString stringWithFormat:#"Test %d", i];
[_childContext insertObject:test]
};
[_childContext save:nil];
4 - do all this in background
5 - use batches to fetch data too .
If you can't understand any point feel free to ask .
For Performing Fast insert without blocking the Main Thread use the above code
//Create Parent Context In Appdelegate
- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
[_managedObjectContext setUndoManager:nil];
}
return _managedObjectContext;
}
//Create Child Context In Appdelegate
- (NSManagedObjectContext *)childManagedObjectContext
{
if (_childManagedObjectContext != nil) {
return _childManagedObjectContext;
}
_childManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_childManagedObjectContext setParentContext:_managedObjectContext];
return _childManagedObjectContext;
}
//Now Perfrom the save operation
[UIAppDelegate.childManagedObjectContext performBlock:^{
//Fill Ur Daatbse (Insert)
for (int i=0; i<count; i++) {
[UIAppDelegate.childManagedObjectContext save:nil];
}
dispatch_sync(dispatch_get_main_queue(), ^{
[UIAppDelegate.managedObjectContext save:nil];
[UIAppDelegate hideProgressBar];
});
}];
I'm completely new to coredata few days back i started using core data. i have written a predicate to fetch data from coredata if i log data in current method it works fine. if i log data in another method it shows "data: "
<Profile: 0x16451ba0> (entity: Profile; id: 0x1633c150 <x-coredata://41971DAD-4658-4C38-9D14-7FDFFA57E032/Profile/p6> ; data: <fault>)
-(void)populateCurrentUserData{
self.blockListArray = [self dataForJid:[[DataManager sharedHandler]userToken]];
Profile *profile = [self.blockListArray objectAtIndex:0];
NSLog(#"Data is :%#",profile.userId);//prints nil
NSLog(#"Data is :%#",self.blockListArray); //"<Profile: 0x17bb23f0> (entity: Profile; id: 0x17bb1fe0 <x-coredata://41971DAD-4658-4C38-9D14-7FDFFA57E032/Profile/p1> ; data: <fault>)"
}
-(NSArray *)dataForJid:(NSString *)inJid{
NSArray *data = [[NSArray alloc]init];
NSError *error;
self.dataArray = [[NSMutableArray alloc]init];
MKAUserProfileCoreData *storage = [[MKAUserProfileCoreData alloc]init];
NSManagedObjectContext *moc = [storage managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Profile" inManagedObjectContext:moc];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
NSPredicate *profilePredicate = [NSPredicate predicateWithFormat:#"userId = %#", inJid];
[request setPredicate:profilePredicate];
[request setEntity:entity];
[request setReturnsObjectsAsFaults:NO];
data = [moc executeFetchRequest:request error:&error];
NSLog(#"Data is :%#",data); //This log works fine
return data;
}
/.h
#import <Foundation/Foundation.h>
#interface MKAUserProfileCoreData : NSObject
{
NSManagedObjectModel *managedObjectModel;
NSManagedObjectContext *managedObjectContext;
NSPersistentStoreCoordinator *persistentStoreCoordinator;
}
#property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel;
#property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, strong, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
#end
/.m
#import "MKAUserProfileCoreData.h"
#import <CoreData/CoreData.h>
#implementation MKAUserProfileCoreData
- (NSManagedObjectContext *) managedObjectContext {
if (managedObjectContext != nil) {
return managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator: coordinator];
}
return managedObjectContext;
}
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel != nil) {
return managedObjectModel;
}
managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
return managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"splashUserProfile.sqlite"]];
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:storeUrl options:#{NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES} error:&error]) {
NSLog(#"Error is %#",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. If it is not possible to recover from the error, display an alert panel that
instructs the user to quit the application by pressing the Home button.
Typical reasons for an error here include:
* The persistent store is not accessible
* The schema for the persistent store is incompatible with current managed object
model
Check the error message to determine what the actual problem was.
*/
abort();
}
return persistentStoreCoordinator;
}
- (NSString *)applicationDocumentsDirectory {
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
//NSLog(#"Path is %#", path);
return path;
}
#end
The problem is that both storage and moc are created locally in dataForJid:. When that method completes, both these variables will be deallocated. Hence the NSManagedObjects become invalid. You need to keep a strong reference to your CoreData stack - for example by making MKAUserProfileCoreData into a singleton, or by building the stack directly in your view controller.
Does your code work fine or have any issues running? Core data faults are not bad, they are a way of saving memory. A core data fault means that the entire object has not yet been loaded into memory. However, as soon as the object is requested, it will be loaded into memory.
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.
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.