Search code examples
ioscore-datansundomanager

CoreData throws exception with NSUndoManager


I have an iPad app where I am using the NSUndoManager with Core Data. Things usually work well, except that there is a semi-reproducible bug when I undo/redo several times. I am only working on the main thread (at least, I have disabled MagicalRecords from using an NSManagedObject on a secondary thread. The problem always occurs if I try to undo/redo an insertion of an NSManagedObject to the context.

So I have something like this:

if (!self.undoManager.isUndoing && !self.undoManager.isRedoing) 
{
    [self.undoManager undo];   
}
else 
{
    NSLog(@"gotcha!");
}

And after several times, I get the following exception. It happens on a secondary thread, which makes me think Core Data is doing something in the background.

CoreData: error: Serious application error.  Exception was caught during Core Data
change processing.  This is usually a bug within an observer of 
NSManagedObjectContextObjectsDidChangeNotification.  _registerUndoObject:: NSUndoManager 
0xcea2d60 is in invalid state, must begin a group before registering undo
with userInfo (null) 2012-07-25 15:42:26.850 TT[3972:3c07] *** Terminating app due to 
uncaught exception 'NSInternalInconsistencyException', reason: '_registerUndoObject::
NSUndoManager 0xcea2d60 is in invalid state, must begin a group before registering undo

Sometimes I am also getting EXEC_BAD_ACCESS, other times just the exception above.

Any idea what could be causing this?

Edit: clarified situation for Mundi (see comments)


Solution

  • Here is the solution that stopped all my crashes: Apparently Magical Records uses privateQueue concurrency by default, and if your code isn't that thread safe, I guess things don't work out. What I did is change it from NSPrivateQueueConcurrencyType to NSMainQueueConcurrencyType for the following method:

    + (NSManagedObjectContext *) MR_contextWithoutParent;
    {
        NSManagedObjectContext *context = [[self alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    return context;
    }
    

    I also found another detail that worked here: Core Data deleteObject: sets attributes to nil

    What I had to do was save the managed object context before adding/deleting a managed object. Kind of weird and inefficient, but saves lots of trouble.