Video hosted by Apple at devstreaming-cdn.apple.com

Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2011-107
$eventId
ID of event: wwdc2011
$eventContentId
ID of session without event part: 107
$eventShortId
Shortened ID of event: wwdc11
$year
Year of session: 2011
$extension
Extension of original filename: m4v
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: [2011] [Session 107] Auto Save a...

WWDC11 • Session 107

Auto Save and Versions in Mac OS X 10.7 Lion

App Frameworks • OS X • 55:33

Your application can easily be part of the new Auto Save and Versions features in Lion. We will show the benefits of adopting these new features, discuss how to implement them, and go over best practices for Auto Saving applications. This talk will also cover some of the other new topics in NSDocument.

Speakers: Tony Parker, Kevin Perry

Unlisted on Apple Developer site

Downloads from Apple

HD Video (406 MB)

Transcript

This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.

Hello, everybody. Thank you so much for coming. This is Auto Save and Versions in Mac OS X Lion. My name is Tony Parker, and I'm an engineer on the Cocoa Frameworks team at Apple. So today we're going to talk about three of the great new features you've been hearing about in Lion. Auto Save, where we free the user from having to worry about manually saving their document.

Versions, which allows the user to go back further in time than just the last version that they've saved on disk. And finally, we'll talk about iCloud integration. And we'll talk about how you can adopt each of these features in your application and really become a first class Lion citizen. So all of these features are integrated into your application in Cocoa via the NSDocument architecture. So first, I'm going to talk a little bit about the basics of NSDocument, and then we'll move on to those three new features.

So here are three examples of a document-based application. There's Numbers, Pages, and Keynote, of course. And in these applications, and in applications like them, the user will open up a document, make changes, perhaps share them with other people, save those changes, and print them and do other things. So a counter example to a document-based application would be something like iPhoto or iTunes, where we call those shoebox applications. Maybe the user has thousands of files on disk that are managed through a single window interface.

So if you're not using NSDoctument yet, let me show you a few of the features that you get for free when you adopt NSDoctument. There is everything in this file menu. We, of course, let you create new documents, open existing documents, and NSDoctument integrates with the Open and Save panel.

We manage the Open Recent menu for you, and that includes integrating with system preferences and respecting the user's preferences for how many documents they want to display there. You can, of course, close documents. And for Auto Save applications, we now have Save a Version, Duplicate, and Revert to Saved will open up the version's UI. NSDocument also provides support for printing.

Every document, of course, is going to have at least one window. And here you see one example. And the window is integrated with the Save panel as well. And in this example, I am saving this document to my Documents folder. And to manage those windows, NSDocument works with NSWindow and NSWindowController to allow the user to manage the windows in terms of what order they are on screen and minimizing and zooming them.

Under the hood, NSDocument provides a lot more features too. There's integration with Undo Manager and tracking of the edited state of the document. In Auto Save applications, that will show up as the new edited indication near the title of the document. And for non-Auto Save applications, that includes the dirty dot in the close button.

NSDocument also does Safe Save for you. That is where we atomically save the document by writing it to the side and then swapping it in as a last step so that the user's data is safe even if a failure occurs during save. And NSDoctument will present errors to the user in the form of sheets and take care of that kind of presentation for you.

Now, new in Lion, we've added a ton of features to NSDocument. That includes support for sandboxing, support for file coordination, which is a new technology we have in Lion for serializing access to files between applications, support for duplicating documents, of course, Auto Save and Versions and Cloud Document Storage, including using the versions UI to resolve conflicts that may occur.

The basic architecture of NSDocument has three main classes: NSDocumentController, NSWindowController, and NSDocument. So let's look at each of these classes briefly and some of their key methods. NSDocumentController's job is to create, open, track, and manage all of the documents that an application has open. It is typically not subclassed and is a singleton object, so you get one using the sharedDocumentController method on the class there. And because it manages the list of documents, you can ask it for a list of all documents that are open.

and his document controller is also responsible for starting the job of creating a new document using this new document method, that's a message that's sent when the user chooses New from the File menu, and also for handling the process of starting opening documents using methods like this one, which is New in Lion and allows a completion handler to be called when the open is finished.

NSWindowController's job is to manage one window associated with a document. In contrast to NSDocumentController, it is typically customized by subclassing. There are two interesting methods, initWithWindowNibNameOwner, that lets you specify the nib file that contains the user interface that you want to display for your document, and the document that's associated with the WindowController.

and finally we have NSDocument itself. Its job is to represent, store and load persistent data. And really it's about where you put your logic to make your document-based application unique. Now in contrast to some of the other classes in the Cocoa frameworks, it is customized by subclassing, not by delegation. And you always subclass it to add your functionality.

When you subclass NSDocument, you have your choice of methods to override to put that logic. And here are two examples. Read from data of type error and data of type error. The first one, you were given an NSData object that contains the state of your document as it existed on disk.

And your job in your implementation is to take that data and make your in-memory representation of your document. Data of type error is the opposite of that, where you create the data from the in-memory representation. And we'll take that data and serialize it to disk for you. Thank you. and his document is also a controller class. So when the user chooses Save from the File menu, then it receives this Save Document message. And we also keep track of properties of the document using methods like File URL and IsDocumentEdited.

So let's look at a graph to see how this fits together. And as Document Controller, our Singleton instance is there at the top. This application has two document subclasses and two documents that are open. There's an RTF document and a plain text document. and each of these document subclasses has their own window controllers, and of course the window controllers manage the windows. And you can see that it's perfectly acceptable for a document to have more than one window open that points to the same document contents.

Another way to understand how NSDoctument works is looking at the message flow that occurs between these three key classes when you perform certain kinds of activities. So first, let's look at what happens when you open a document. So in the NSDocumentController, it's going to receive the Open Document message because the user has chosen the Open menu item.

Oh, and here we're going to go through some of the key methods, not every method involved, just the key ones. So once the user has chosen one or more documents, because of course you can open many documents from that Open panel, we get the list of URLs they chose by calling URLs from running Open panel. And for each of those documents, we will open them, starting with Open Document with contents of URL display error.

Now, these documents can be of any of the types that your application supports, so we need to find out what kind of NSDocument subclass to use. We figure that out and type for contents of URL error. And then finally, we call make document with contents of URL of type error to begin the process of creating the NSDocument subclass.

So here it's a MyRTF document. Now, in that class, we're going to initialize it, of course, using init with contents of URL. And then we're going to call read from URL of type error. And that will begin the process of reading the document. That starts with read from URL of type error, which we'll call read from file wrapper of type error, or read from data of type error, depending on which of those two methods you've overridden in your subclass. Next, we add the document to the list that the NSDocument controller is managing, make the window controllers, including calling init with window nibname owner, and then finally, show the windows associated with the document. All right.

Let's look at a second example that is what happens when you save a document. So here, let's assume that the document has been named by the user, and they've chosen Save from the File menu. And this document will receive the Save Document message. That, in turn, will call Saved URL of type for Save Operation Completion Handler.

The URL we already know because the document has been named. The type we already know because of the kind of document it is. The Save Operation parameter can be Save, Save As, Auto Save. There's a few different options there. and that completion handler again will be called when the save is finished.

The first step of saving is calling WriteSafelyToURLofType for SaveOperationError, and this is where NSDoctument implements the safe save logic that we give you for free. Part of WritingSafely is WriteToURLofType for SaveOperationOriginalContentsURLError, which will then call WriteToURLofTypeError, which calls DataOfTypeError or FileWrapperOfTypeError, depending again on which one you've overwritten.

So the key point here is that, of course, you, again, you customize NSDocument by subclassing. And when you subclass, you can override at any point along this list of save methods or write methods, and in a similar fashion when you open documents. Now, when you override at the beginning of this list, at the top, you can put more custom behavior into the NSDocument architecture because you're cutting off a lot of the functionality below it.

The flip side of that, of course, is that the more, the lower the method on this list you override is, the more functionality you get for free. And when we add document features to NSDocument, like Auto Save, Versions, and iCloud integration, the lower you are in this list, the easier it is for your application to adopt those new features. So we recommend that when subclassing NSDocument, you start at the bottom and only work your way up if you find that you need something only available at the previous level.

Okay, with the basics out of the way, let's talk about the first major new feature, and that's Auto Save. So here you see an example of an Auto Saving application. The edited menu in the middle there allows the user additional options that they didn't have before. And really, Auto Save is about saving automatically at key times.

[Transcript missing]

We wanted to make Auto Save as easy to enable in your application as it is for users to use it. So to turn it on and get a ton of functionality, you only need to override one method in NSDocument, and that's Auto Saves in Place, Return Yes.

And when you turn this on, just try it out. If you take one thing away from this talk, I want you to put this method in your NSDocuments subclass and see how much functionality you get for free. This enables Auto Save, this enables versions, it adds a whole host of behaviors that we're going to talk about soon. So it's really simple, just that one method. And of course, you should try it out in your application and see what happens. And to show us what happens in an example app, I'd like to invite my colleague Kevin Perry up to give us a quick demo.

Good afternoon, I'm Kevin Perry, another engineer on the Cocoa Frameworks team. And here we have the classic sample app sketch. And this is before we've done any modifications for Lion. And so when I edit a document, The contents that you see of the document on screen, we typically think of that as being different than the document on disk.

and David are going to talk about how to use the Dirty Dot in the Closed button. We indicate this state to the user with the Dirty Dot that's up here in the Closed button. And again, when you close the document, we ask you, do you want to save this before you close the document? All this happens because we put the responsibility of saving on the user. So let me show you how we can give responsibility for saving instead to the framework and free that from the user.

So like Tony said, all we need to do here in our NSDocument subclass is override Auto Saves in place and return yes. So let me just enable that and we'll run. So now when I edit a document, you can start to think about what's on screen being the same as what's on disk at all times. We indicate this state to the user in a different way in Lion, specifically this edited indication in the title bar.

This is helpful not only to know when you've edited your document, but in case you make an accidental change, you can notice that and either undo your changes or revert to a previous version. Also, now when we close a changed document, an edited document, You're not warned about saving. The framework has either already done it or does it when we close the document so the user doesn't even have to think about it.

Like Tony showed you before, there are also some changes to the File menu to adapt to this new mental model of saving. Instead of Save, we have Save a Version. Instead of Save As, we have Duplicate. Instead of Save a Copy As, we have Export. And Revert to Saved brings you into Versions, where you're able to browse previous versions of your document, You can even select and copy something from a previous version and paste it in the current one. or you can choose to completely replace the current version with a previous one.

So this is a lot of great functionality you can get with very little code in your document subclass. And again, as Tony said, we encourage you just to go try it out and see how it works. I'll show you a little bit later how a little bit more code can get you even more great features. Now back to Tony.

Thanks, Kevin. So you can see just that one method and all that functionality completely for free in Sketch. Now, as easy as it is to turn on, there are still a few things you can do to really polish the Auto Save user experience once you've enabled it. We're going to talk about being smart about when you save, allowing the user to cancel certain kinds of saves, and a major new feature in NSDocument in Lion to allow saving on background threads.

So first, being smart about when to save. So as we mentioned, NSDoctument will save at key times and trigger an Auto Save when the application quits, when the document is closed. Of course, we need to make sure the contents are safe before the application goes away. We also Auto Save when the user switches away from your application, and that's because it seems like a good time to avoid interrupting the user and making sure the document is consistent with what's on disk.

In Lion, we still have the Auto Save timer that you're familiar with from Snow Leopard and earlier. However, we've improved it. So now it automatically readjusts itself based on user activity. So the idea is that the timer will not fire while the user is actively editing their document.

We also save before the user reverts, and the reason is because we need to keep the current contents in case they change their mind about the revert they've just done. And a key point here, your application can also trigger an auto save. You just call the save methods with that auto save operation type. Now, you know your application best. We've done some key points here. But if you know of a place in your application or a time in your application where you can save without interrupting the user, then go ahead and do it yourself too.

So part of being smart about when you save is adoption of file coordination. Now as I mentioned, file coordination is a new technology in Lion that serializes access to files between applications. And if you use NSDocument, you're in luck. We implemented it all for you. That means that we will Auto Save automatically when a read is requested and notify you and reread the contents when a write is completed. So an example of the former is if you mail a document, then mail may ask your application to please save right now so that we can have a consistent version of the document.

And another example of the latter there is if iCloud has synced a new version of your document down, we need to make sure the user sees on screen what exists on disk. File coordination is such a key technology to Auto Save, Versions, and iCloud support that we have an entire talk devoted to it. It's called Taking Advantage of File Coordination. It's in this room right after this talk.

So the second method of polishing the Auto Save user experience is to spend less time saving. So with Auto Save enabled, of course, we are going to save much more frequently than in the past. And the key point is to avoid interrupting the user. The last thing we want is for users to use Auto Save enabled applications and feel like their entire time they're spent staring at a beach ball.

So one way you can do that is to only save what's changed. And one way you can only save what's changed is to use NSFileWrapper. NSFileWrapper stores the resources in their most natural format, and it allows quick copies. That's because the user is probably editing, say, the text component of your document and not the movie, audio, or picture file.

So we can change only the text part, and the other resources can be used -- we can use hard links to make sure that they're not copied unnecessarily. If you have a monolithic flat file format, then the whole thing has to be written every time. NSFileWrapper helps you with that problem.

So there's another method to avoid interrupting the user, and that's what we call a cancelable Auto Save. So to explain what it is, first let me tell you what a normal Auto Save is. That happens when the application quits or when the document closes or if it's been too long since the last save.

And the reason for that last one is because we want to make sure the user doesn't starve our readjusting timer and avoid -- prevent the document from being saved. But the other kind of save then, Auto Save, is an implicitly cancelable Auto Save. And right now in Lion, we trigger those only based on the Auto Save timer.

So the idea here is that, you know, because the timer readjusts itself, we can try to put off saving while the user is interacting with their document. But clearly we can't see the future. So if the user edits the document and then waits, then the timer fires, and then they decide to interact with the document, we want to avoid interrupting them in that case, too. So we can do that via this new technique.

So here's an example of how you might use it. In my write to URL of time, I'm going to type error implementation. While I'm saving, I'm going to do some work related to the save. And then every so often check to see if auto saving is implicitly cancelable.

If it is, then we should also check for user activity. And the implementation of that function is going to be highly application dependent. One example of what you might do could include running the main run loop, looking for user events, and then if you see one, decide that now might be a good time to cancel.

Once you've decided that it's appropriate to cancel, you should abort the save. And then you can go back to the main run loop, look for user events, by creating a new error, NSError, excuse me, error with domain, Cocoa error domain, ns Cocoa error domain, and the ns user canceled error, and then return no from this method.

It's important to use the Cocoa error domain and the user canceled error, because what happens is that when you return no and you use that kind of error, ns document will unwind the save and avoid presenting the user with an error that says, hey, you just canceled a save. The whole point was to do it silently. So use this particular error domain and code.

So next we have this major new feature in NSDocument, which is support for asynchronous saving. And we've tried to make it as easy to enable as Auto Saving itself. You can see here you override, again, one method in your NSDocument subclass. It's called can asynchronously write to URL of type for save operation and then return yes. And of course, because there's some parameters there, you have a choice about what kind of save operations or type of documents you want to support saving. We recommend you support as many as possible.

So to show you why it's important to use asynchronous auto saving, let's look at what happens during a synchronous auto save. The user edits the document, and that timer will start, because the document is now dirty and needs to be saved. And at some point, the timer will fire, and the user must wait.

[Transcript missing]

Here are the methods involved. The save operation will start on the main thread using saved URL of type for save operation completion handler. At that time, the main thread will block. The saving will be started on the background thread starting with write safely to URL of type for save operation error. And then after the background thread has snapshotted the contents of the document, it should allow the main thread to continue using unblock user interaction. And the background thread will finish saving. And that write and finish step can take a long time on NSDocument.

So, we can have a huge win by adopting this. When we introduce asynchrony into Auto Saving, there are a couple of concerns. The first is that there are two categories of resources that need to be serialized. The first is the user. Anything that may present UI to the user must be serialized.

That includes saving, reverting, duplicating, printing, or any other activities that your application does. The second category is file access. And things in that category include the URL, the modification date of the file, the on-disk contents of the file, and the edited state of the document, because the edited state compares, in a way, the on-disk contents to the in-memory contents.

So we have a way for you to coordinate between threads when you're doing saves and using these kinds of activities. The first is if the main thread needs to present something to the user, and only the main thread may present anything to the user, of course. You should surround that work with perform activity with synchronous waiting using block. The first parameter is a hint to the app kit.

Tell us whether or not this method should return or not before that block parameter is executed. If you're up on your block syntax, you can see that the block parameter takes a block parameter of its own. That's called the activity completion handler. In your block, you put the work that you need to be done that may present UI to the user, and before you leave that block, you should call the activity completion handler.

If any thread, main or otherwise, needs access to the document file on disk or any of the properties of that file, you should do that work within one of these methods. Perform synchronous file access using block and perform asynchronous file access using block. And you see the second one also takes a completion handler block.

and David . Let's see how you might use those. Here is an example of asynchronous file access. Let's say your application has a requirement that as soon as the save is finished, we have to compress the contents of that document and then copy it to another place on the file system.

That is clearly work that doesn't need to block the user from interacting with the document and it could take a while, so it seems like a great candidate to do on a background thread. So let's do that. We will start a background operation and on that background thread, we will start asynchronous file access using those methods.

Inside the block, we are going to perform that background work and then we are going to call the completion handler and make sure that we end asynchronous file access. And in this way, we ensure that the main thread does not touch the file at the same time as the background thread is.

Another potential thing to be concerned about when you're using asynchronous saving is the potential of deadlock since we have these serialized resources. And we need to make sure that we coordinate access between user activities and file access. So what I mean is that you should use these NSDoctument methods. The first is called continue asynchronous work on main thread using block. This is for file access work. And the second is called continue activity using block, and that's for user activity kind of work.

So I'll explain by way of another example. So let's say that after saving, we're going to calculate the file size of the newly saved document. And we're going to do that work on a background thread too. And once we're done, we're going to display that value to the user in an inspector panel. And of course, that's main thread work because it's displaying UI. So let's take a first stab. In my background operation, I'll start asynchronous file access. And inside that block, I calculate the file size.

But before it's done, the user decides to go ahead and duplicate the document. Well, duplicating a document requires file access as well because we need to make sure that we have the current contents of the document. So we start asynchronous file access on the main thread. And then the background thread says, okay, I'm going to run this stuff on the main thread using perform selector on thread or NSOperation main queue or any kind of technology like that. And at this point, we've wound up in a state of deadlock.

And the reason is because the background thread is waiting on the main thread. To do the UI work. And the main thread is waiting on the background thread to give up its asynchronous file access. So don't do this. Instead, use those NS document methods I just talked about. We're going to use continue asynchronous work on main thread. And this document is smart enough to figure out that that work must be allowed to continue to avoid a deadlock.

So the update user interface that the background thread wanted will complete. And the background thread is allowed to end its asynchronous file access. Which, of course, will allow the main thread to finish its duplicate work and end the synchronous file access. So we avoided the deadlock scenario. Okay. Oh, yes, do that with the check mark. So, to give us a demo of how you can adopt some of these Auto Save polishing techniques, I'd like to invite back Kevin.

Let's go back to Sketch and enable asynchronous saving. Sketch implements the data of type error method that Tony talked about earlier in such a way that, you know, it's thread safe. So all we need to do to enable asynchronous saving is... and David Override can asynchronously write to URL of type for save operation and return yes.

This causes the data of type error method to be invoked on a background thread. However, NSData doesn't know, NSDocument doesn't know implicitly that our implementation of this method is thread safe. And so, as Tony mentioned before, we have to block the main thread until this method returns, because this is the prepare step of the save.

Normally this won't be a problem. If you just have a few graphics in your Sketch document, the serialization into NNS data and the writing to a relatively fast file system won't take a noticeable amount of time to the user. However, if you have many graphics in your document, this won't scale so well because the user will start to notice that it's taking a long time to serialize all these graphics into NNS data and they may encounter the wait cursor. Let's simulate this.

by inserting a good old sleep at the end of this method. So we've enabled asynchronous saving and our data of type error method is going to take a long time to run. So now when we make a change to this document and force a save, you notice we're having to wait. And boom, wait cursor. Not a great experience. However, we can do a lot better than this by using unblock user interaction to make the prepare step a lot shorter.

In order to call unblock user interaction, we need to make sure that what we're doing on this background thread won't conflict with what's potentially happening on the main thread while the user is continuing to interact with the document. The data that we need to generate the NSData object is this graphics array right here. However, this array is not only mutable, but its contents are mutable as well.

or we can snapshot this document state by making a deep copy, like so. Now that we have this document snapshot, we know that this array and its contents are not going to change even while the user is continuing to interact with the document on the main thread. So let's go ahead and use this array everywhere it was used before.

So now that we have this snapshot, we can call self-unblock user interaction, which will unblock the main thread immediately and allow the user to continue interacting with the document much sooner. So now when I make a change and save, you see I'm still able to interact with the application, even make edits and so forth. And then sometime in the background, after that five second sleep completes, the save is done and the user didn't even notice it.

Thank you. So let's go ahead and now add a new feature to Sketch. Tony mentioned it. A file size field here in the inspector. You see I've added. And every time a save completes, we want to update this with the current size of the document's file. and we want to do this in a way that is, we don't want to block the main thread as we're accessing the file system, since that is proper behavior. And we also want to avoid the potential deadlock that can happen that Tony talked about before.

So, first thing we need is to know when saving completes. And we can do that by overriding save to URL of type for save operation completion handler. And if we call super, we'll pass in all the same arguments, but we have a special completion handler of our own. After we call the normal completion handler, We're going to want to go onto a different thread so that we can perform our file access in a way that won't block the main thread potentially. We can use that using NSOperationQueue, add operation with block.

We also need to make sure that we're the only ones accessing the file at this particular time so that the answer, the file size, doesn't change unexpectedly out from under us. So we can use perform asynchronous file access using block. Once we're inside this block, we have that exclusive access to the file. So now we're safe to access the file system and get the file size. We do that using NSURLs, get resource value with the NSURL total file size key. In this case, we just have a single file. So we don't need to do any more than this.

Now that we have the new file size, let's update the UI. We can't do it on this thread because we can only update the UI from the main thread. So how do we get back to the main thread? Well, you might think NSOperationQueue or PerformSelectorOnThread or something like that.

But like Tony mentioned before, we're still holding on to this file access. And if something on the main thread in the meantime has attempted to get that file access and is waiting for us to relinquish it, and we just call something on the main thread, we're going to end up with a deadlock.

In this case, you may argue that this is simple enough that we could just relinquish file access before calling onto the main thread, and you might be right. But in some cases, you're not gonna be able to do that. You're gonna need to hold on to exclusive file access until you're done with all your work on the main thread as well. So let's work under that assumption now.

In order to avoid the deadlock, instead of using Dispatch Async or NSOperation Queue or any of those technologies, we're going to use NSDocument's continue asynchronous work on main thread using block. Like Tony showed you, this is able to resolve the deadlock and have this block invoked on the main thread, even if something else is waiting on the main thread for file access.

Now that we're safely on the main thread, we can update the UI by updating our IVAR and sending the proper KVO notifications. I've set up bindings between the inspector and the document. Last but not least, we need to make sure that we call the file access completion handler, or else something else that comes in in the meantime and tries to get file access will end up waiting forever. So now let me show you this.

I'll make another change to the document, force a save, and after our five seconds of wait time, We see that the file size is updated here in the UI. Remember, we did this in a way that prevents accessing the file system from the main thread, which could cause unexpected, unpredictable blocking. And we also prevented, avoided the deadlock that can happen when continuing that work on the main thread.

So let's move back to the topic of asynchronous saving. And I made some measurements to show you just how beneficial asynchronous saving can be. I created on my machine a 10,000 graphic sketch. And with synchronous saving, this took about .676 seconds. Not too long, but, you know, as you add more graphics, this will get even larger.

Just by turning on asynchronous saving, I was able to reduce this to 0.649 seconds. Now, that doesn't seem like a huge improvement. However, I was on a very fast file system, my local hard drive. If I was working on something like a network-mounted volume, this could take arbitrarily longer to complete the save. And in the synchronous case, the user would have to wait for the entire duration.

With the asynchronous case, since we're just waiting until the datative type error method returns, The amount of time doesn't change at all. So the potential gains from using asynchronous saving are much greater. However, by snapshotting the document state and calling unblock use interaction early, we're actually able to reduce the amount of time that the user had to wait to a dramatic .011 seconds.

Thank you. So I hope this is a good example of how valuable it is to not only turn on asynchronous saving, but go the extra mile and snapshot your document state so that you can unblock the user as soon as possible. So that when an Auto Save happens while the user is in the middle of interacting with the document, they don't have to wait suddenly for that Auto Save to complete. All right, we're going to go back to Tony now, who's going to talk to us about some more cool features. Thanks, Kevin.

Okay, let's move on to versions. So, again, here is the UI that you've seen many times by now. Of course, there's a two-up interface, one current document, one old document, and many old documents behind it. We have our done and restore buttons along the bottom and the timeline along the right.

So, this interface and versions in general is designed to expand the number of options the user has for reverting their document. They can go way further back in time than was previously possible, and they can also do a kind of selective revert, as you've seen in a few demos already, including Kevin's, where you can copy something from the old document because it's a fully live document file and then paste it into the current document. Tony Parker, Kevin Perry Furthermore, the versions browser protects against unintentional change. So if you open up a file and make some changes to it, and then change your mind, you can always get back to the state as you left it by using the versions browser.

Under the hood, version uses compressed storage. So if your file changes by just a few bytes, we're not going to store an entirely new copy of the file. And NSDocument will automatically manage the list of versions for you. So by that I mean that NSDocument may create a version for an hour ago, two hours ago, or three hours ago, but it will also automatically purge old versions. So a year ago plus an hour, two, and three hours, we probably don't need to keep all four of those, so we can reduce that down to one. So that kind of logic NSDocument does automatically for you.

And furthermore, the Versions Browser is the first way for your Cocoa application to be integrated with Time Machine. So if we run out of versions that are stored on the local disk, we're going to go ahead and look on backup disks as well for older versions of that document. And if we find them, they're displayed right in line in the UI as just another version of the document.

So, Versions is on by default when you enable Auto Saving in place, but like Auto Save, there are a couple things you can do to polish the version's user experience. The first is to adopt asynchronous document opening, or concurrent document opening. This is a feature that we added in Snow Leopard, and I'm not going to go into a lot of detail about it today, but we are opening many copies of the document, so if we can open them at the same time instead of serially, that's a big win for browsing in Versions.

You can also show only the relevant controls in the Versions browser. We allow you a mechanism to resize the windows in the Versions browser to their best viewing dimensions, and we have a new technique for allowing what we call "discardable changes." So first, let's talk about showing a different interface. So as you can tell, the Versions UI is about providing a focused interface, and that means that we want to allow the user to see as much of their document as possible, not necessarily edit as much of their document as possible.

So you can participate in this and improve the experience of your app in Versions by hiding things like toolbars or inspectors. You can also add a new interface that may not be fully relevant to the experience of looking at the content. You can do that in a few ways. Here's one. Listen for these notifications that are posted when the Versions browser enters and exits. We have NSWindow will enter a Versions browser notification and a similar did exit.

You can also find out if certain kinds of controls should be just flat out disabled. If you're looking at the document on the right here, it is in what we call viewing mode. Viewing mode documents are not allowed to be edited. So if you have something which is purely for editing, you can find out if your document is in that mode and just go ahead and disable it right away.

To participate in resizing the windows for that interface, you can use this NSWindow delegate method. Window will resize for version browser with Mac's preferred size, Mac's allowed size. So, you know, versions has this two-up user interface. And to show two documents at a time, we would show two documents at the Mac's preferred size. And we also have a Mac's allowed size, which is the maximum size until we, let me say that a different way. The screen size is not the maximum allowed size. We need to leave room for the user interface.

So, the Mac's allowed size is the maximum size of one NSDocument window. And if your document is larger than the preferred size and less than the allowed size, we scale it in the user interface so that it fits in the two-up interface. And then when the user begins to interact with that window, we scale it back up again so they can see it at full size. So, this method lets you participate in deciding what the size of your window should be.

New in Lion, we also have a way to allow changes to certain kinds of changes to viewing mode documents. The reason you need it is because changes to viewing mode documents are automatically undone if the change is discardable. The reason is we don't want to allow the user to attempt to edit the past. That's not something that's possible.

If you need to allow the change, then the change must be discardable. Discardable changes are normally auto saved, but on locked or viewing mode documents, they may be discarded without warning. So another example, in Keynote, the currently selected slide, as well as the hidden or shown state of a group of slides, is something that is both undoable and preserved in the document file.

Now clearly, in viewing mode, you would need to allow Keynote to choose, the user to choose different slides to view. We can't just automatically undo that. So that action of changing the slide or expanding groups of slides is something that is a great candidate to be a discardable change. And a bonus of that is that if the document is locked, then the user doesn't have to unlock the document just to change the slide. We allow that too. Another example in a similar kind of thing is the currently displayed chart in the Numbers application.

To use discardable changes, in the same place that you would set the group name for your undo group, like undo typing, change slide, or move shape in Sketch, you can now set action is discardable, yes, and NSDocument takes care of all the rest for you. If it's not an undoable change, you can use NSDocument's update change count mechanism. You just bitwise or the change you normally would have done, usually NSChange done, with NSChangeDiscardable.

So one more note about versions. If you don't want to use the versions browser, which is integrated into NSDocument, we have foundation level access to the list of versions as well. And that's via a class called NSFileVersion. When you use NSFileVersion, it's important to remember that you as the caller are responsible for file coordination and version management.

Let's take a quick look at some of the API. To get an array of NSFileVersion instances for a file, you call other versions of item at URL. This is in contrast to the current version. You can add a new version of a file to versions using this method, add version of item at URL with contents of URL, options, error.

You can revert to an old version of a file using an NSFileVersion instance by using replace item at URL options error. And NSFileVersion instances have plenty of properties about the document, including the name and modification date, which can, of course, change over time. So to show us another demo of how you can use versions in your application, I'd like to invite back Kevin.

Thank you. So, Sketch again. I showed you earlier how just by turning on Auto Saving in place, we gain basic version support for Sketch. However, there's a couple of things we can do to improve the experience. First of all, notice that when I go into versions, the tool inspector gets pushed off the side of the screen. This provides a clutter-free environment for the user to browse versions. However, currently, AppKit doesn't bring that inspector back when we come out of versions. Now, this may change in the future in AppKit, but in the meantime, we want to do the right thing.

Another thing is the window size. If I happen to resize the window to something like this for whatever reason, and the versions doesn't know that the window size isn't optimal. And so we end up with the canvas partially clipped and a bunch of extra wasted space. So let's solve these two problems using the APIs Tony talked about. Here we are in the SKT window controller class, which is also our Windows delegate.

And we can use window delegate APIs. to know when Version Browser enters, will enter, or did exit. In the window will enter Version Browser delegate method, we'll check to see if the palette is currently visible. and when we exit the version browser, if it was visible, then we show it again. Pretty simple.

For the window sizing problem, we implement the delegate method window will resize for version browser with Mac's preferred size, Mac's allowed size. and the size that we return from this method is the size that versions will resize the window to before entering. And here we use NSView, NSScrollView and NSWindow APIs to determine the proper optimal size for our document. So let me show you how these work.

And we see the window resize so that it fits to the content very nicely. And when we exit versions, the tool inspector is brought back as we would expect. So these are just some very simple ways to use these APIs. There is a lot of potential here for your own applications to improve the user experience in versions such as doing things like hiding user interface elements that aren't needed like tool bars or sidebars and so forth. So that's all for versions. Now we'll hear about iCloud from Tony.

Kevin, thanks. Thank you. So on to iCloud. and his document is integrated with the new Cloud Storage feature in many ways. NSDocument will use the versions browser to resolve conflicts, as I mentioned earlier. And NSDocument's adoption of file coordination is going to update the contents of your document on demand when new things are synced down from the Cloud. We're going to briefly cover the API to move a document to the Cloud and finding documents in the Cloud.

So to move a document to the cloud, the APA is not on NSDocument but on NSFileManager. There you need to first find your container. Each application has their own Ubiquiti container. And here it is, URL for Ubiquiti container identifier. And once you have it, you can make a document ubiquitous simply by calling setUbiquitous itemAtURL destinationURL. The itemAtURL parameter will be the document you're moving, and the destinationURL should be someplace in your Ubiquiti container.

Next, to find iCloud documents, the API is on NSMetadataQuery, which is our class for integrating with Spotlight as well. So there we have a new search scope, NSMetadataQuery Ubiquitous Document Scope. And also you will find some API and NSURL that's helpful, including finding out if an item is ubiquitous or not, looking at if it's downloaded, if it's downloading, the percent that it's downloaded, and similar keys for uploading. So to show you another example of how that works in Sketch, I'd like to invite up one last time Kevin.

Thank you again. So here is another version of Sketch, which I've modified to provide cloud integration. Here in the File menu, you see there's two new menu items, Open Cloud Document and Move to Cloud. So let's go ahead and move the currently open document to the cloud. and I'll make another change here that we will be able to see on the cloud.

and David . Welcome to WWDC. So now let me switch to another machine. Okay. Not really. But we're really exercising the APIs here. And we have the same version of Sketch running here. So we'll go to File, Open Cloud Document, and we can open the document that we just saved on the other machine. and let's be daring here and even make a change on this machine at 2011. I'll switch back. And there we see the change is synced to the original machine as well.

So let's see these APIs that Tony talked about in action. In our document subclass, we override validate menu item because we want to change what the move to cloud file menu item says, depending on whether or not the item is actually in the cloud already. We can detect this using isUbiquitousItemAtURL on NSFileManager. And if it is ubiquitous, then we change the title to Remove from Cloud instead of Move to Cloud. And we also set some state on the menu item so that when it's selected, we know what operation we're actually performing.

This is implemented in move to or from cloud. The key method here is right here. And as file manager, set ubiquitous item URL, destination URL error, as Tony mentioned. When we're making an item ubiquitous, or moving it to the cloud, then we build the destination URL by using File Manager's URL for Ubiquity Container Identifier, passing in the identifier that belongs to our applications, in this case, com.apple.sketch. And documents generally live in a document subdirectory inside of the container, so we append that here. After making sure that the directory exists, we append to that URL the file's name.

Down here, before we call setUbiquitousItem at URL, you notice we have some file coordinator code. And in the future, you won't have to do a lot of this code, but for now, it's required to make this work. So I won't cover it in great depth. But the key thing here is this method again.

The menu item for finding ubiquitous documents in the cloud is here in our skt-appdelicate class. When the application finishes launching, we're going to call startUpdatingCloudDocumentMenu, which creates an NSMetadata query using the new ubiquitous document scope. This causes NS Metadata again to search the cloud instead of your local file system for files.

[Transcript missing]

Then we set up observers for when the results array of the metadata query changes. And when we get those notifications, we will update the Cloud Documents menu by adding a menu item for each document that we find, which in turn, when it's selected, will open that document. And that's really all there is to it.

These are the most important APIs that I've shown you on NSFileManager and NSMetadataQuery, but there are the others that Tony showed you as well for seeing progress of uploads and downloads and even purging the document on the local hard drive without removing it from the cloud and so forth. So now let's go back to Tony, who's going to wrap up for us.

Okay, thanks, Kevin. That's pretty cool, right? I mean, it wasn't that difficult, just a few lines of code, and we got something synced across the cloud. So let's do a quick recap of what we talked about today. We really want to help you make your document-based application a first-class Lion citizen.

That means adopting Auto Save. And again, here's the one method you need to take away from this talk. Auto Save's in place, return yes. Try it out, test your app, and come talk to us in the labs and tell us how it went for you. After it's enabled, you can polish Auto Save by saving fast and adopting asynchronous saving.

We also talked about versions, and remember, it's on by default when you enable Auto Saving, but you can polish it too by streamlining the interface in browsing mode and allowing discardable changes. And then finally, we looked at iCloud, and we talked about how to move documents to the cloud, how to find documents in the cloud using NSMetadataQuery, and then we looked at how you could find document properties using NSURL.

So for more information, there's of course Bill Dundee, our App Frameworks Evangelist. The documentation and developer forums are great resources. And if you've ever looked at NSDocument.h, you know that it's a lot of comments. We measured. There's 87% comments in that file. They're designed to tell you everything about every method in there and how it works with everything else.

So the documentation, the header files, our release notes are all great resources to find out more information about all the great new features we've added in Lion. We have a couple of related sessions. The first one I already mentioned, taking advantage of file coordination in this room right now.

Then also resume and automatic termination in Lion. That's Marina, Thursday at 9 a.m. Those features go with these like peanut butter and jelly. So they all work great together or taste great together in Lion. So with that, I want to thank you so much for coming to this talk. And I'm really looking forward to seeing what you guys do with Auto Save, Versions, and iCloud.