Over the years, I’ve written a few different data models. Some of them were even (gasp) documented! Taking the time to nail the data model stack early on in app development has really helped with overall development efficiency and the ability to roll with the punches. The more recent hand implemented data model stacks evolved to have some common features:
- Data driven schema definitions
- Some form of relational data representation
- Files are first class entities
- Ability to perform incremental loads / be made partially resident
- The ability to represent inter-file entity references
- Support for Undo / redo support
- Support for Change tracking / change management
- XML file format
Moving from the .Net world to Cocoa the obvious data model stack choice was Core Data. Super promising and drool worth feature list!
- Data driven schema definitions
- Yup, can do.
- Some form of relational data representation
- Yup, can do.
- Yup, can do.
- Files are first class entities
- Nope, you have to roll your own file referencing, but no biggie.
- Nope, you have to roll your own file referencing, but no biggie.
- Ability to perform incremental loads / be made partially resident
- As long as you use SQLite as your backing store, but that’s cool.
- As long as you use SQLite as your backing store, but that’s cool.
- The ability to represent inter-file entity references
- Nope, you have to roll your own, but no biggie.
- Support for Undo / redo support
- Yup, can do (or at least so I thought).
- Support for Change tracking / change management
- Yup, can do with KVC/KVO (or at least so I thought)
- XML file format
- Not if you want incremental load / save, but SQLite is hand debuggable enough.
Learning curve aside, the happy turned to sad as I started running into what turned out to be pretty common problems with Core Data’s support for undo. Cocoa has a pretty slick undo paradigm. Not exactly gang of four, but pretty cool. The problem comes with the way Core Data interacts with it. After a few days of spelunking and pain, I built enough vocabulary to ask Google the right questions, and the answers were not promising.
Take this blog post by Will Shipley (the Delicious Library guy):
“It’s pretty obvious I should be managing my OWN undoManager, turn off the one in CoreData, and just use CoreData for what it is EXTREMELY good at, which is minimal change tracking and fetching and storing data VERY VERY quickly.”
And then this other blog post by Mike Abdullah:
“Turning off undo registration doesn’t remove your changes from this system, it just stops the undo manager hearing about them. The change is still undone the moment the user wants to go back to a point before you made the change.”
…
“Sadly I don’t have a complete solution yet, but continue to ponder it. Do you have an idea? Get in touch, I’d love to hear from you.”
The kinds of problems I ran into were having Core Data KVO notify me in a redo that a relationship was being reconnected, but a query on the NSManagedObject in question would not yet be populated with any fields that I could use to index / sync with my controller or view representation. Trying to force Core Data to sync with the undo system in bite sized chunks (i.e. subscribing to NSUndoManager notifications to hear about begin/end undo groups and calling NSManagedObjectContext’s processPendingChanges) to guarantee ordering doens’t work reliably (i.e. that insert object->set value->add gets undone in reverse order, and then redone in forward order).
Digging in further, CocoaDev mailing lists posts from purported Apple engineering dudes seem to indicate really isn’t much guarantee that Core Data will fire off KVO in undo/redo in rational orderings. The problem is related to what Mike Abdullah describes in his blog post above: that Core Data implements undo / redo like commits and rollbacks to an SVN repository. Reading a few other blog posts like Will’s regarding spending years fighting Core Data’s undo system, I opted to turn it off and register my operations with NSUndoManager manually. Unfortunately, my first attempt failed miserably.
My first approach was to capture the insertions, property changes, set additions/removals and deletions in the NSUndoManager. A promising method on NSManagedObjectContext “insertObject” seemed like it would happily reverse “deleteObject”. Unfortunately, instrumenting the save process and inspecting the NSManagedObjectContext’s pending insertions, deletions and registered objects proves this to be false. Some more Cocoa Dev posts seem to confirm that while this will work with an XML backed persistent store, it won’t work with an SQLite backed store. Without the ability to reverse an insert without recreating a new NSManagedObject, the tactic of capturing subsequent property changes and set additions/removals was mooted. Time for my second approach.
My second approach was still to capture insertions, property changes, set additions/removals and deletions in the NSUndoManager, but using a primary key on each NSManagedObject rather than depending on the NSManagedObject to remain constant. In this case the primary key is a string representation of a UUID (hey, what can I say, I’m an ex-Microsoft programmer). Now, each operation in the undo/redo queue incurs a fetch request to find the NSManagedObject it needs to operate on. Sucesss! This method (while not the most performant undo/redo in the world) works reliably.
The next problem I ran into was saving. Seems simple enough, but when loading a document, editing, saving, editing and saving a second time, Core Data deadlocks. WTF? Again, Google had a not very promosing (albiet not fatal) answer for me in the way of a post from Apple dude Ben Trumbell:
“There’s a known issue on the current Leopard using NSPersistentDocument with an SQLite store under Garbage Collection. Change any one of the 3 pieces in that configuration and things will work fine. We will be addressing the issue.”
Sweet, and that was from 2007. Well, there is no freaking way I’m not going back to unmanaged programming, and the features offered by SQLite are more than half the reason I’m interested in Core Data, so don’t let the door hit you on the way out NSPersistentDocument!
On the bright side after all this spelunking, it was only about a ten minute task to create a (minimalistic) alternative implementation of NSPersistentDocument supporting Save, Save As, etc.

[...] When a user undoes a deletion it calls the primitive accessor which does not emit KVC/KVO notifications. I get the feeling that the obvious workaround of manipulating observers in the primitive accessors is a bad idea and is likely to just open another can of worms. I also get the feeling that this issue is similar to other people’s previously discussed issues and that I’m going to need to take over the undo/redo responsibilities from the NSArrayController (as mentioned in this post: http://cocoaconvert.net/2009/05/07/core-data-rough-riding/). [...]