Package-Based Core Data Documents

It's taken a heck of a long time, but a couple months again, Apple finally published details on how to use NSPersistentDocument for a package-based document. This is something that we do in Sandvox 1.5 and caused much headscratching on how best to achieve it. It's good to have some kind of an official guide since most every implementation out there (that I've seen) has some subtle bug somewhere in it.

Things still aren't perfect - using a file wrapper could get pretty memory intensive - and atomic saves go out of the window, but it's a start. I guess as Apple points out in the ReadMe, the level of atomicity required is up to you. I wondered if one could get clever and represent every resource in the document package as a persistent store. i.e. create a tiny managed object model for each resource, some kind of NSAtomicStore subclass, and add them to the persistent store coordinator as required. But even then, I'm not sure how atomic save operations would be.

I guess part of the problem with any sort of package-based app (or maybe any document-based application!) is the degree of protection to offer against the user abusing the existing document on disk. How should the app behave if the document is deleted, or placed on a disk which is then removed? Should it flat out refuse to save the document again until a suitable file is returned to the original location, or should an attempt be made at guaranteeing that some base level of the document remains available?

Introducing CKHTTPConnection

Again. Or CKHTTPConnection II perhaps. Wait Mike, back up.

As the handful of you that read this blog know, I've been spending a fair bit of time lately working on ConnectionKit. Recently we pushed out 1.2 and 1.2.1 releases of the framework; improving the API, particularly in the area of authentication. At the same time I've been pondering over the question of ConnectionKit's future and toying around with a version 2.0 of the framework.

Now ConnectionKit 2.0 is nowhere near ready for primetime,  but it does contain a component that may be useful to others so I thought I'd blog it here for feedback. I've written a brand new CKHTTPConnection class (CK 1.x contains an identically name class, but with a totally different purpose).

What is it and why might I want it?

CKHTTPConnection provides a sort of "NSURLConnection lite" API. All the core methods are there, but it deals only with HTTP connections, and strips out cookie handling & caching. Also it runs asynchronously on a single thread unlike NSURLConnection's internal use of a worker thread that manages all connections.

So, it's a crappy version of a mature class already in the Foundation framework. Not really selling it am I.

Where this really comes in handy is for applications which use HTTP, but for more than data fetches. Perhaps you have a custom communications protocol which is built atop HTTP for simplicity. If so CKHTTPConnection's simplicity and level of control can be a big win.

Finally the class has one more trick up its sleeve. For anyone performing POST or PUT operations, NSURLConnection is pretty weak as it provides no API for tracking upload progress. CKHTTPConnection has a handy -lengthOfDataSent method. This could make it a big win for adding progress tracking to an existing codebase; just drop in CKHTTPConnection (it has a virtually identical API) and implement the new delegate methods.

Why are you telling me this?

As I know there are potential uses outside of the ConnectionKit framework for this class, and 2.0 will not be ready for some time, I thought it would be good to get the word out. I'm very interested in feedback on any ways CKHTTPConnection could be improved to match others' usage. For example, this has the potential to be fantastically helpful for tracking upload progress, and you might require cookie support in tandem with that (presumably for authentication purposes). I'd be happy to add it if so.

Where can I get it?

While 2.0 is being developed on its own branch:

CKHTTPConnection.h
CKHTTPConnection.m

Access to the main repository is at http://opensource.utr-software.com/source/connection/

It's all available under a BSD style license.

Core Data and the Wonderful World of Undo Management

Core Data takes a great weight off your shoulders as a developer by providing automatic support for undoing and redoing changes. But things get a bit gnarly the moment you step off the beaten track. So prompted by a question on Stack Overflow and my regularly forgetting the details, I proudly present a quick guide to making Core Data behave right for your model when it comes to undo management

Standard Undo Support

We should all be familiar with how this works. You set the value of a Core Data property, or insert or delete an object, and the NSManagedObjectContext automatically records this with its undo manager. When the user hits the undo menu item your objects are internally updated to their original state, and send KVO notifications to match (note that the changes do not go through any accessor methods you might have written).

Suspending Undo Registration

This is all well and good, but of course there's that vital moment where you realise some property shouldn't be available in the undo menu. There's nothing in Core Data to handle this directly; instead you need to go talk to the NSUndoManager. What's this, a handy -disableUndoRegistration method? Perfect! Except it's not that simple. Here's what you actually have to do:

[[self managedObjectContext] processPendingChanges]
[[[self managedObjectContext] undoManager] disableUndoRegistration]
// Make your special changes to the managed object
[[self managedObjectContext] processPendingChanges]
[[[self managedObjectContext] undoManager] enableUndoRegistration]

You see, to stop potentially registering 1000's of tiny changes with the undo manager all at once, Core Data intelligently batches them up into a single registration. This is performed by the -processPendingChanges method, and usually occurs as the undo manager is about to hit a checkpoint. So to side-step that mechanism, your code needs to make sure any pending changes go through before undo registration can be turned off. Then your changes can be made, and the whole process repeated again to turn registration back on. Somewhat of a hassle sure, but fairly logical in the end.

But wait, there's more! You see Core Data's automagical undo/redo support actually operates pretty similarly to a version control system. Hitting undo is a lot like telling SVN that you want to go back to the revision prior to that which you have checked out right now. 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.

So let's say the user makes some change to a property of a managed object. And then let's say an NSTimer comes along a little later, turns off undo registration and performs a change of its own. Now if the user hits undo, BOTH of those changes will be undone at the same time. In reality, this approach just winds up effectively tacking the change onto the end of the previous changeset.

Totally unintuitive of course, but I swear it does make sense under some circumstances!!

Stepping Outside the System

So what then if you really do need to make a change to a property that is outside the undo mechanism? For a purely transient property, not a problem! It's easy to forget that managed objects can happily use traditional instance variables. Rather than defining it in the managed object model, just create an instance variable and corresponding accessor methods. Oh, and don't forget to make use of the faulting methods to jettison the value if needed.

There's still another case remaining; what of when you need a persistent property that isn't undoable? Something whose value depends on the world outside of Core Data and so once set cannot be undone.

For example Apple's Pages application records the date a document was last printed. Printing cannot be undone (clearly!), and so this property is exempt from the undo stack. Yet it is still persisted in the document. We need something similar in Sandvox too, for noting whether a page needs publishing or not.

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. I'll be updating this page as things develop. Some possibilities so far:

  • Create a separate context without an undo manager. Make and save changes there as needed. Major downsides are the added complexity, including how that saving a document now requires saving to MOCs - what if the second save fails? We can't undo the first save.
  • -setPrimitiveValue:forKey: will update an object's state without notifying the undo manager, but it also means the context won't notice that the object is updated, meaning it probably won't be saved.
  • Assuming you're operating within the standard document architecture, any solution is going to require overriding NSDocument's change tracking in some way so that once one of these changes has taken place, -isDocumentEdited always returns YES until you save the doc. In which case the same mechanism can be used to track the updated values and ensure they are persisted.

Give Up

Finally, for some applications you could just follow Wil Shipley and the Postal Service's advice. Stop trying to persuade Core Data to behave as you want. Turn it off (-[NSManagedObjectContext setUndoManager:nil]) and write your undo management code as you would for a non-Core Data app, directly in your accessor methods or controller code.

-[NSConference release]

This week I was very pleased to attend NSConference, kindly put on by Scotty and Tim Isted. All the speakers were wonderful, and well aimed at the audience (nothing too beginner oriented!). Add into the mix a good venue, decent catering and a bunch of like-minded developers to socialise with and it's not hard to see you're onto a winner. I fully intend to be present again next year.

As is always the case, the meetup has left me inspired to do some maintenance round here, so you'll notice a new ConnectionKit update and KSExtensibleManagedObject release.

Thanks again to everyone involved!

ConnectionKit 1.2

With the release of Sandvox 1.6.1, we've taken the decision to merge our latest and greatest ConnectionKit work back on to the trunk. You can grab in-progress development from the trunk at any point. From the tags directory, you now have a choice of 1.0 (the original ConnectionKit API), 1.1 (Brian Amerige's error-handling updates), and 1.2 (improved API, used by Sandvox 1.6).

Some of the changes I've documented already:

Since the original post, I've made an important change to how connections are created. Rather than directly create a connection with a URL, you use the CKConnectionRequest/CKMutableConnectionRequest API. This functions almost identically to NSURLRequest, making connection objects pretty much immutable. So your workflow is either:

Construct URL > Build connection request > Create connection object > Start connection

or:

Ask CKConnectionRegistry for connection object > Start connection

© Mike Abdullah 2007-2009