Targeting Leopard, using blocks when available

A scenario cropped up recently where I want to pass a block into an API when running on Snow Leopard and greater, but fall back to the old block-less API before that. Sounds simple enough, but launch the app on Leopard and what’s this?

Symbol not found: __NSConcreteStackBlock
Expected in: /usr/lib/libSystem.B.dylib

Uh-oh. What to do? I tracked down this Stack Overflow answer for the same problem with iOS3, and applied it to Sandvox. It works! In Xcode simply add this to your target’s “Other Linker Flags”:

-weak_library /usr/lib/libSystem.B.dylib


Resetting a managed object context doesn't always do what you might expect

As noted in my previous post, NSPersistentDocument implements reversion by calling -reset on the managed object context. I was playing with this for Sandvox, and ran into somewhat of a problem with it, which may be an unusual edge case; I'm not sure!

We're using the binary store, and also implementing old-style autosave — whereby a copy of the document is periodically saved in case the computer dies or Sandvox crashes.

If you attempt to revert the document after it's been autosaved, the file on disk does indeed go back to the correct reversion (in fact it's not touched at all). But the context only reverts back as far as the autosaved state. What's happening here?

It seems that, at least for atomic stores, -[NSManagedObjectContext reset] only resets the state of the context. It doesn't reread the data from disk, instead relying on its own existing, cached copy of the data.

Fortunately I was able to figure out a workaround; rather than reset the context, do something like this:

The trick, basically, is to replace the existing store with a new one, thus triggering a refresh of Core Data's cache.

A major downside of doing this mind, is that the context will be temporarily unusable while there is no store attached, so make sure all controllers etc. depending on the context are able to handle this, or torn down (as NSPersistentDocument does)

Reverting NSPersistentDocuments

NSPersistentDocument is documented to implement some extra work on top of what NSDocument provides. The docs say:

Revert resets the document’s managed object context. Objects are subsequently loaded from the persistent store on demand, as with opening a new document.

That sounds a little painful! -reset is a pretty hardcore thing to do; how will my UI cope? Perhaps the description for -revertToContentsOfURL:ofType:error: will help?

Overridden to clean up the managed object context and controllers during a revert.

Ooh, cleaning up controllers sounds pretty magical! But, er, how? What? Are we talking about NSArrayController etc.? Who knows!

Finally, the Lion release notes have this to say:

• Overriding -revertToContentsOfURL:ofType:error:

When you enable autosaving in place you also enable version preserving. (Unless you also override +preservesVersions to turn version preserving off.) With autosaving in place enabled it's more important than ever to invoke super when you override -revertToContentsOfURL:ofType:error:, because it's NSDocument's default implementation of that method that updates the document's state to reflect what happens during reverting to an old version. If you don't, NSDocument might present the user with alerts about the document having been changed by another application when that is not the case.

Does NSPersistentDocument call super, or provide its own, equivalent (one hopes!) implementation?

Well, fortunately for you, I have done some digging — albeit in the pub, so bear that in mind.

How does NSPersistentDocument revert the managed object context?

It does indeed call -reset

How does the UI cope?

Before resetting the context, all window controllers are torn down. After the reset, -makeWindowControllers is called to recreate whichever windows the document pleases.

I think this is a reasonable compromise to make; while Cocoa is theoretically capable of keeping the same window intact and updating it to match the reverted model, that's going to be quite a pain to implement. Plus under Lion, with animated window opening and closing, the effect is quite instructive/pleasing.

Is -[NSDocument revertToContentsOfURL:ofType:error:] called?

Yes indeed, which in turn calls -readFromURL:ofType:error:, so make sure if you've overridden that, you can handle reading having already happened.

How does all this come into play with Versions on Lion?

When using the Versions browser, if the user chooses to revert back to an older copy of the document, Cocoa will call -revertToContentsOfURL:ofType:error: on your existing document. It won't take the easy route of throwing away the the document and starting again; in fact I can't find a suitable API for going down this route if you'd prefer.

© Mike Abdullah 2007-2012