NSPasteboard and simple custom data

There are two ways to use NSPasteboard.

The older way: Pin data to the board yourself

First, you must declare the types that you will put on the pasteboard. You also appoint an object that will “own” the pasteboard, meaning that this is the object putting stuff on the pasteboard.

The next step is to put data on the pasteboard. This step is optional.

“Optional?!”, you ask. Yes: If you don't put data on the pasteboard for any type you have declared, and the pasteboard subsequently needs that data (to paste/drop), then the pasteboard will ask you (the owner) for it. This is called promising that data, and it's good when that data is expensive to copy (large) or generate.

There are five ways to put things on the pasteboard (besides getting asked for them):

  • As a string. Only good for plain text. The pasteboard will handle converting it to various encodings as needed.
  • As a property list. Only good for property lists (and yes, this is enforced all the way down, so an array of images doesn't count), or things that you can convert to and from property lists. This can include your own objects, if you implement that in them.
  • As raw data. Good for existing data types, such as image types (PNG, JPEG, etc.) and A/V types (MPEG-4, etc.).
  • As the contents of a file identified by path. Only good if what you're dragging/copying is already a file.
  • As the contents of a file wrapper. If you're not already using file wrappers, you can safely ignore this.

The newer way: Pin objects to the board and let them turn themselves into data

The new hotness, introduced in Snow Leopard, is to make your objects themselves able to write themselves to a pasteboard. This does require that they know everything about themselves that you will want on the pasteboard, including identifiers.

You need to make your objects conform to both NSPasteboardWriting and NSPasteboardReading.

The writing protocol will look really familiar now that you know the older way. The pasteboard asks your object what types it would represent itself as, then asks it for a property-list object for each type. (The protocol also provides a way to promise types instead of having the data for them requested immediately.)

To copy objects that conform to NSPasteboardWriting to the pasteboard, send the pasteboard a clearContents message (required in the new way, optional in the old way), then writeObjects: passing an array of the objects you want to copy.

The reading protocol is, as you'd expect, the inverse. To paste, you send the pasteboard a readObjectsForClasses:options: message. The pasteboard asks each of those classes what types it would recognize, then (optionally) tries to instantiate one or more of them from what's on the pasteboard.

The downside of this, particularly where reuse identifiers are concerned, is that it can end up breaking the separation of your model layer from your controller layer. Your reading initializer will need to know what to do with an identifier if you want it to return the existing object that has that identifier. That means it needs to either talk to the controller (bad) or duplicate the controller's lookup code (worse).

I don't know of a good way to implement move drag-and-drop (including, but not limited to, reordering) with the new protocols without running into this problem. For copy drag-and-drop (including, but not limited to, cross-process), it's fine, since you don't need identifiers for that—just generate the data on one end and create the new copy from it on the other.

The upside of the newer way is that handling of multiple items is much more sane. In the older way, there's only one “item”—indeed, not really any concept of items at all—in multiple types. If you wanted to copy or drag multiple things, you made an array and copied that as a single property list for some type, then re-created/retrieved the multiple things from that single property list on the other end. The newer way explicitly supports one or more items; when copying multiple things, you simply pass them all to writeObjects:.

Your case: A single NSUInteger identifier

Box it in an NSNumber (which is a property list) and use it in the older way.