Question

I have a Cocoa document-based app and running into some problems with NSUndoManager even though I'm having Core Data take care of all of it.

So every time a new persistent document is created I create a root context and a child context. The root context is responsible for writing to the persistent document and the child context is the context that I modify via create/edit/delete NSManagedObjects. I set the root context as the parent context. I also set the undo manager of the child context to the original context that was created with the NSPersistentDocument. Here's some code to hopefully make it a bit clearer:

// create root context
NSManagedObjectContext *rootContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];

// startContext is the context created with the document
[rootContext setPersistentStoreCoordinator:[startContext persistentStoreCoordinator]];
NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

// set root as parent
[childContext setParentContext:rootContext];

// set undo
[childContext setUndoManager:[startContext undoManager]];

The reason I do all of this is because I ran into a similar problem as described here: Enable saving of document NSManagedObjectContext immediately?

So I bring this up because this is the only code in my application where I even touch the NSUndoManager. I am testing my app by simply inserting NSManagedObjects and then Undo-ing the insertion. Sometimes after two undos, or maybe five, or maybe even ten I receive the following error:

_endUndoGroupRemovingIfEmpty:: NSUndoManager 0x100159f30 is in invalid state,   endUndoGrouping called with no matching begin
2013-01-29 21:31:23.375 TestApplication[30125:303] (
0   CoreFoundation                      0x00007fff8a54f0a6 __exceptionPreprocess + 198
1   libobjc.A.dylib                     0x00007fff8215f3f0 objc_exception_throw + 43
2   CoreFoundation                      0x00007fff8a54ee7c +[NSException raise:format:] + 204
3   Foundation                          0x00007fff80ea021f -[NSUndoManager _endUndoGroupRemovingIfEmpty:] + 195
4   Foundation                          0x00007fff80ea0154 -[NSUndoManager endUndoGrouping] + 42
5   Foundation                          0x00007fff80ed79da +[NSUndoManager(NSPrivate) _endTopLevelGroupings] + 447
6   AppKit                              0x00007fff8253632d -[NSApplication run] + 687
7   AppKit                              0x00007fff824dacb6 NSApplicationMain + 869
8   TestApplication                     0x0000000100001512 main + 34
9   TestApplication                     0x00000001000014e4 start + 52

So if I'm reading the debugging information correctly then I need to call [[context undoManager] beginUndoGrouping] however the problem with this is that no where in my program do i use the `[[context undoManager] endUndoGrouping]'. Has anyone experience this before?

Any help is appreciated.

Was it helpful?

Solution

Each context in OSX creates an undoManager by default (nil in iOS). The reason for the grouping error was that your child context undoManager was trying to nest changes inside the rootContext's undoManager which was nested inside the startContext undoManager (which was the same as the child context's undoManager).

// create root context
NSManagedObjectContext *rootContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];

// startContext is the context created with the document
[rootContext setPersistentStoreCoordinator:[startContext persistentStoreCoordinator]];
[rootContext setUndoManager:startContext.undoManager];
NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

// set root as parent
[childContext setParentContext:rootContext];

OTHER TIPS

For me, it seems to be caused by sending begin/endGrouping message during undo/redo handling. I disable undo registration temporarily to remove this error message.

    [[NSNotificationCenter defaultCenter] addObserverForName:NSUndoManagerDidUndoChangeNotification object:undoManager queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {

            [undoManager disableUndoRegistration];

            // Some code for undo containing undo grouping

            [undoManager enableUndoRegistration];

    }

I did some more digging and found this. In my project some sorting operation is done in a child managed object context running on a background thread and I tried to undo the whole process including the sorting one in the main thread (top level context). I could resolve this by moving the sorting operation to the background thread as it was during undo/redo.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top