Core Data NSInternalInconsistencyException on single background thread delete function - objective-c

My project has a database handler and one of its function is delete almost all records from the database. I execute the actual deleting on a single background thread. Sometimes my code works fine sometimes it crashes with a NSInternalInconsistencyException. Currently it fails 1 out of 3 tries. Why am I getting a NSInternalInconsistencyException? I thought this only happens when your multi-threading your core data function. Here's my code:
//Database handler init.
- (id)init
{
self = [super init];
if(self) {
self.appDelegate = [UIApplication sharedApplication].delegate;
//getting the managedobjectcontext from appdelegate
self.managedObjectContext = self.appDelegate.managedObjectContext;
//background queue
self.backgroundQueue = dispatch_queue_create("database.queue", NULL);
status = 0;
}
return self;
}
//delete majority of the database entry
- (void) clearTables
dispatch_async(self.backgroundQueue, ^(void) {
NSError *error;
//update this entity records.
for(Entity1 *object in [self queryEntity:#"Entity1"]) {
object.download_ymdhms = nil;
[self.managedObjectContext save:nil];
}
[self.managedObjectContext save:&error];
NSLog(#"error1: %#", [error localizedDescription]);
//Delete the other entities data.
for(Entity2 *object in [self queryEntity:#"Entity2"]) {
[self.managedObjectContext deleteObject:object];
}
[self.managedObjectContext save:&error];
NSLog(#"error2: %#", [error localizedDescription]);
for(Entity3 *object in [self queryEntity:#"Entity3"]) {
[self.managedObjectContext deleteObject:object];
}
...
for(Entity10 *object in [self queryEntity:#"Entity10"]) {
[self.managedObjectContext deleteObject:object];
}
[self.managedObjectContext save:&error];
NSLog(#"error3: %#", [error localizedDescription]);
//notify deletion complete
[[NSNotificationCenter defaultCenter] postNotificationName:#"database.queue.delete.done" object:nil];
});
}
//get all the objects in an entity.
- (NSArray *) queryEntity: (NSString *)entity {
return [self queryEntity:entity withPredicate:nil withLimit:0 orderBy:nil];
}
...
//general query
- (NSArray *) queryEntity: (NSString *)entity withPredicate: (NSString *)predicate withLimit: (int)limit orderBy: (NSString *)order
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:entity inManagedObjectContext:self.managedObjectContext]];
if(predicate.length > 0) {
[fetchRequest setPredicate:[NSPredicate predicateWithFormat: predicate]];
}
if(limit > 0) {
[fetchRequest setFetchLimit:limit];
}
if(order.length > 0) {
[fetchRequest setSortDescriptors: #[[[NSSortDescriptor alloc] initWithKey:order ascending:YES]]];
}
NSError *error;
NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
return results;
}
This is the error that I sometimes get:
CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. statement is still active with userInfo (null)
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'statement is still active'
Edit: added code comments to make it more readable.

Related

NSManagedObjectContext crashing when accessed on external thread

I'm currently having a threading issue with the managedObjectContext within my application. Currently, I have a background thread running that MUST be in the background, but accesses the managedObjectContext at the same time. Another ViewController calls on the method processAllApplications shown below that then calls checkCompletedApplicationsFor24HourExpiration which then calls getAppsWithStatus. The thread seems to be currently locked causing this operation to halt where the warning below is. I need a way to process this through and am quite a noob when it comes to Core Data. Would anyone be able to advise. I was reading that I may have to create multiple instances of my managedObject and merge them. How would I go about that if that is the case?
AppDelegate:
- (NSManagedObjectContext *)managedObjectContext
{
[__managedObjectContext lock];
if (__managedObjectContext != nil) {
[__managedObjectContext unlock];
return __managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
__managedObjectContext = [[NSManagedObjectContext alloc] init];
[__managedObjectContext setPersistentStoreCoordinator:coordinator];
}
[__managedObjectContext unlock];
return __managedObjectContext;
}
- (NSMutableArray*)getAppsWithStatus:(int)intStatus {
NSLog(#"%i on main thread getAppsWithStatus", [NSThread currentThread].isMainThread);
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Application" inManagedObjectContext:self.managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
// Set example predicate and sort orderings...
NSNumber *status = [NSNumber numberWithInt:intStatus];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"status = %# && username = %#", status, [[NSUserDefaults standardUserDefaults] objectForKey:#"username"]];
#warning FAILS HERE INTO ABYSS
[request setPredicate:predicate];
NSError *error = nil;
NSMutableArray* applications = [[NSMutableArray alloc] initWithArray:[self.managedObjectContext executeFetchRequest:request error:&error]];
for (Application* eachApp in applications)
eachApp.applicationNumber = nil;
[self saveDB];
return applications;
}
- (void)processAllApplications:(id)userInfo {
[self.processApplicationsLock lock];
if ([[NSUserDefaults standardUserDefaults] objectForKey:#"username"] == nil) return; // Not logged in
NSLog(#"processing");
[self checkCompletedApplicationsFor24HourExpiration];
[self alertFor12HourCompletedApplications];
[self alertForExpiredDraftApplications];
if ([DeleteAllDraftApplicationsForCurrentApplicationYear isSatisifiedByDate:[DateTimeFactory currentApplicationDate]]) {
[self deleteExpiredApps];
}
[self performSelector:#selector(sendApplications:) withObject:nil afterDelay:3];
[self.processApplicationsLock unlock];
}
- (void)checkCompletedApplicationsFor24HourExpiration {
NSLog(#"OutboxSender - (void)checkCompletedApplicationsFor24HourExpiration");
NSLog(#"%i on main thread checkCompletedApplicationsFor24HourExpiration", [NSThread currentThread].isMainThread);
NSArray* completedApps = [self getAppsWithStatus:STATUS_COMPLETED];
NSDate* targetDate = [self offsetDate:[DateTimeFactory currentApplicationDate] withDay:-1 withMonth:0 withHour:0];
for (Application* theApplication in completedApps) {
if ([MoveCompletedApplicationToDraftApplicationSpec isSatisfiedByApplication:theApplication cutOffDate:targetDate]) {
NSLog(#"Sending To draft with date: %#", theApplication.submittedDate);
theApplication.status = [NSNumber numberWithInt:STATUS_DRAFT];
[self deleteSignatures:theApplication];
}
}
NSString* message = [NSString stringWithFormat:#"%i completed application/s have been sent to drafts", [completedApps count]];
echo_Alert(#"", message);
[self saveDB];
}
create separate managed object context
+(NSManagedObjectContext *)getManagedObjectContext
{
NSManagedObjectContext *managedObjectContext;
#try {
NSPersistentStoreCoordinator * coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[managedObjectContext setPersistentStoreCoordinator: coordinator];
}
}
#catch (NSException *exception) {
NSLog(#"Exception occur %#",exception);
}
return managedObjectContext;
Use this separate managed object context in your fetching method,
- (NSMutableArray*)getAppsWithStatus:(int)intStatus {
NSMutableArray * mutableObjects;
NSLog(#"%i on main thread getAppsWithStatus", [NSThread currentThread].isMainThread);
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Application" inManagedObjectContext:[self getManagedObjectContext]]; // Here use separate managed object context
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
// Set example predicate and sort orderings...
NSNumber *status = [NSNumber numberWithInt:intStatus];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"status = %# && username = %#", status, [[NSUserDefaults standardUserDefaults] objectForKey:#"username"]];
#warning FAILS HERE INTO ABYSS
[request setPredicate:predicate];
NSError *error = nil;
NSMutableArray* applications = [[NSMutableArray alloc] initWithArray:[[self getManagedObjectContext] executeFetchRequest:request error:&error]];
NSMutableArray * resultedArray = [applications mutableCopy];
NSMutableArray * objectIds = [[NSMutableArray alloc] initWithCapacity:[resultedArray count]];
for (NSManagedObject *obj in resultedArray) {
[objectIds addObject:obj.objectID];
}
mutableObjects = [[NSMutableArray alloc] initWithCapacity:[objectIds count]];
for (NSManagedObjectID * objectID in objectIds) {
NSManagedObject * obj = [self.managedObjectContext
objectWithID:objectID]; // Here use self.managedObjectContext in which you already created.
[mutableObjects addObject:obj];
}
for (Application* eachApp in mutableObjects)
eachApp.applicationNumber = nil;
[self saveDB];
return mutableObjects;
}

Update issue with Core Data

Trying to update some Core Data. The data is actually updating "somewhere", but its not saving/updating the db.
- (IBAction)Update:(id)sender {
NSEntityDescription *entityDesc =
[NSEntityDescription entityForName:#"Preferences"
inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesc];
NSError *error;
NSArray *objects = [context executeFetchRequest:request
error:&error];
if ([objects count] == 0) {
// No update, didnt find any entries.
} else {
for (NSManagedObject *obj in objects) {
[obj setValue:_salesPrice.text forKey:#"value"];
if(![context save:&error]){
NSLog(#"Saving changes failed: %#", error);
}
}
}
//[context save:&error];
}
I've tried [context save:&error]; in the commented area, but still no save. I also get no error on save.
You use only 1 NSManagedObjectContext? Your naming convention is not ideal. Usually you would name the entity Preference, since its one object. Try the following code.
CoreDataAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
// This is for completion. Usually you should not get the context from the App Delegate.
// Its better to pass it from the App Delegate to
// the initial view controller via a property (dependency injection).
NSFetchRequest *req = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([Preferences class])];
NSError *error = nil;
NSArray *preferences = [context executeFetchRequest:req error:&error];
// Check error
if ([preferences count] == 0) {
// No update, didnt find any entries.
} else {
for (Preferences *preference in preferences) {
[preference setValue:_salesPrice.text forKey:#"value"];
}
}
[context save:&error];
// Check error

Saved Core Data does not persist after app closes 80% of the time

I started dealing with Core Data lately, and in my tests, I've found that about 20% of the time the data actually gets saved to the DB. The rest of the time, it's only saved temporarily, while the app is running. If I restart, the last data I saved gets lost.
Does anyone know what the problem could be?
Here's the code:
//Save data
NSEntityDescription *users = [NSEntityDescription insertNewObjectForEntityForName:#"Users" inManagedObjectContext:document.managedObjectContext];
[users setValue:#"Name Test" forKey:#"name"];
[users setValue:[NSNumber numberWithInt:20] forKey:#"age"];
[users setValue:#"Some Country" forKey:#"location"];
//Debugging
//no error ever shows up
NSError *error;
if(![document.managedObjectContext save:&error]) {
NSLog(#"Error: %#", error);
}
//this is just to show that the problem may not be with my UIManagedDocument (self.document), since the NSLog never gets called.
if(self.document.documentState != UIDocumentStateNormal) {
NSLog(#"Document is not opened");
}
//End of debugging
//Fetch all the data from the entity
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:#"Users"];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
fetch.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *results = [document.managedObjectContext executeFetchRequest:fetch error:nil];
NSLog(#"Results on the database: %d", [results count]);
document is the same thing (at least I hope so, in most of the cases) as self.document; it's just an argument of the method where this code is located.
Here's the code for my .h and .m:
.h:
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
#interface CoreDataViewController : UIViewController
#property (nonatomic, strong) UIManagedDocument *document;
#end
.m:
#import "CoreDataViewController.h"
#implementation CoreDataViewController
#synthesize document = _document;
- (void)fetchStuff:(UIManagedDocument *)document {
//Save data
NSEntityDescription *users = [NSEntityDescription insertNewObjectForEntityForName:#"Users" inManagedObjectContext:document.managedObjectContext];
[users setValue:#"Name Test" forKey:#"name"];
[users setValue:[NSNumber numberWithInt:20] forKey:#"age"];
[users setValue:#"Some Country" forKey:#"location"];
//Debugging
//no error ever shows up
NSError *error;
if(![document.managedObjectContext save:&error]) {
NSLog(#"Error: %#", error);
}
//this is just to show that the problem may not be with my UIManagedDocument (self.document), since the NSLog never gets called.
if(document.documentState != UIDocumentStateNormal) {
NSLog(#"Document is not opened");
}
//End of debugging
//Fetch all the data from the entity
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:#"Users"];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
fetch.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *results = [document.managedObjectContext executeFetchRequest:fetch error:nil];
NSLog(#"Results on the database: %d", [results count]);
}
- (void)useDocument {
if(![[NSFileManager defaultManager] fileExistsAtPath:[self.document.fileURL path]]) {
[self.document saveToURL:self.document.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success){
if(success == YES) NSLog(#"created");
[self fetchStuff:self.document];
}];
} else if(self.document.documentState == UIDocumentStateClosed) {
[self.document openWithCompletionHandler:^(BOOL success) {
if(success == YES) NSLog(#"opened");
[self fetchStuff:self.document];
}];
} else if(self.document.documentState == UIDocumentStateNormal) {
[self fetchStuff:self.document];
}
}
- (void)setDocument:(UIManagedDocument *)document {
if(_document != document) {
_document = document;
[self useDocument];
}
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if(!self.document) {
NSURL *url = [[[NSFileManager defaultManager]URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"Database"];
self.document = [[UIManagedDocument alloc]initWithFileURL:url];
}
}
#end
Note: There's also my data model, which has an entity called "Users", with the attributes age, location, name.
The data was being saved sometimes because of the autosaving, which happens each X (maybe 10, I need to check on documentation) seconds. To force the save, I should've used this:
[documentation saveToURL:documentation.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {
if(success == YES) NSLog(#"Awesome, it's saved!");
}];
Although it works fine adding this code to fetchStuff:, it'd be better to implement this when the user exits the screen, since it could be automatically saved via autosave.
You should not call save on the UIManagedDocument MOC. Here's an edited version of your code. Please try it.
//Save data
NSEntityDescription *users = [NSEntityDescription insertNewObjectForEntityForName:#"Users" inManagedObjectContext:document.managedObjectContext];
[users setValue:#"Name Test" forKey:#"name"];
[users setValue:[NSNumber numberWithInt:20] forKey:#"age"];
[users setValue:#"Some Country" forKey:#"location"];
// Removed save on UIMDMOC - We let auto-save do the work
// However, we have to tell the document that it needs to
// be saved. Now, the changes in the "main" MOC will get pushed
// to the "parent" MOC, and when appropriate, will get save to disk.
[document updateChangeCount:UIDocumentChangeDone];
if(self.document.documentState != UIDocumentStateNormal) {
NSLog(#"Document is not opened");
}
//Fetch all the data from the entity
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:#"Users"];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
fetch.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *results = [document.managedObjectContext executeFetchRequest:fetch error:nil];
NSLog(#"Results on the database: %d", [results count]);
OK... That was not too painful... just removed the call to save, and replaced it with a call that tells the UIManagedDocument that some changes have been made, and need to be saved.
Record *newentry = [NSEntityDescription insertNewObjectForEntityForName:#"Record" inManagedObjectContext:self.mManagedObjectContext];
newentry.code = entryStr;
NSError *error;
if ([self.mManagedObjectContext save:&error])
{
NSLog(#"save successfully");
}
else
{
NSLog(#"fail to save");
}

Objective-C => Can't update existing objects in CoreData

I can't update objects in my database using core data, this my function :
- (void) saveItem:(NSDictionary*)dico {
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Item"
inManagedObjectContext:managedObjectContext];
Item *item =(Item *)[entity ReadSingleForKey:#"identifier"
value:[dico valueForKey:#"identifier"]
inContext:managedObjectContext];
if (!item) {
item = [[[NSManagedObject alloc] initWithEntity:entity
insertIntoManagedObjectContext:managedObjectContext] autorelease];
item.identifier = [dico valueForKey:#"identifier"];
}
item.title = [dico valueForKey:#"title"];
NSError *error = nil;
if (![managedObjectContext save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}else{
NSLog(#"No error found.");
}
}
Even if "item" is not nil the object in the database doesn't change & I got always "No error found.".
- (NSManagedObject *) ReadSingleForKey:(NSString *) key
value:(id) value inContext:(NSManagedObjectContext *) context{
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:self];
[request setPredicate:[NSPredicate predicateWithFormat:#"%K = %#", key, value]];
[request setFetchLimit:1];
NSError *error;
NSArray *arr = [context executeFetchRequest:request error:&error];
if (arr && [arr count]) {
return [arr objectAtIndex:0];
}
return nil;
}
Any idea ??
There are several problems with your code that make it difficult to determine the error.
1) No error handling.
2) Obscure private method ReadSingleForKey - what does it return?
3) item defined as type Item and as different type NSManagedObject in same method.
Put in NSLog statements or breakpoints to examine the values of dico and item. You will soon find the place where you go wrong.
Another potential source of this error is how you read the data from the database later. For now I am assuming that this is working correctly.
The problem was in my Item class :
I was using #synthesize instead of #dynamic

iOS: [self.fetchedResultsController performFetch:&error]; makes my app to crash

I have a UITableViewController, and I want to feed it with the content of a core data model.
However, when I fetch the content my app crashes. This is the init method (I pass a NSManagedObjectContext to it).
- (id)initInManagedObjectContext:(NSManagedObjectContext *)context
{
self = [super initWithStyle:UITableViewStylePlain];
if (self) {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"Document" inManagedObjectContext:context];
request.predicate = nil;
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"idDoc"
ascending:YES]];
/*
NSError *error = nil;
NSManagedObject *retrievedDocument = [[context executeFetchRequest:request error:&error] lastObject];
NSLog(#"retrievedDocument %#", retrievedDocument);
*/
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:nil
cacheName:#"CollectionCache"];
self.fetchedResultsController = frc;
[frc release];
[request release];
//HERE IT CRASHES
NSError *error;
[self.fetchedResultsController performFetch:&error];
if (error) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
//exit(-1); // Fail
}
}
return self;
}
I'm sure the context is correctly passed because if I uncomment the commented snippet, the stored data are correctly printed.
My guess is that something is wrong with the fetchedResultsController.
thanks
The exception was related to your wrong use of performFetch:
It returns a BOOL that tells you the success of the fetch. If you get a NO back you are allowed to check the NSError object. Otherwise you must not touch it.
Probably all the methods that use &error should be used like this:
NSError *error;
if (![self.fetchedResultsController performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
//exit(-1); // Fail
}
The exception was not related to fetchedResultsController but due to the not initialized NSError
NSError *error = nil;