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 may have transcription errors.

Hello, everybody. Thank you so much for coming. This is autosave inversions 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. AutoSave, 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 NS Document architecture. So first I'm going to talk a little bit about the basics of NS Document 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 NSDocument yet, let me show you a few of the features that you get for free when you adopt NSDocument. There is everything in this file menu. We, of course, let you create new documents, open existing documents, and NSDocument 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 autosave applications, we now have save a version, duplicate, and revert to saved will open up the version's UI. And this document 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 autosave applications, that will show up as the new edited indication near the title of the document. And for non-autosave 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 in this document 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, autosave 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 shared document controller 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 this 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, it 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. And this 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 "is document edited."

So let's look at a graph to see how this fits together. And this document controller in 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 NSDocument 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 OpenDocument message because the user has chosen the OpenMenuItem. 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 OpenPanel, we get the list of URLs they chose by calling URLs from running OpenPanel. And for each of those documents, we will open them, starting with OpenDocument 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 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 NSDocumentController is managing, make the window controllers, including calling init with window NibNameOwner, And then finally, show the windows associated with the document.

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, autosave, 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 NSDocument implements the safe save logic that we give you for free. Part of writing safely is writeToURLofType for saveOperationOriginalContentsURLError, which will then call writeToURLofTypeError, or writeToURLofTypeError, which calls dataofTypeError or filewrapperofTypeError, depending again on which one you've overwritten.

So the key point here is that, of course, 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 lower the method on this list you override is, the more functionality you get for free. And when we add features to NSDocument, like autosave 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 autosave. So here you see an example of an autosaving application. The edited menu in the middle there allows the user additional options that they didn't have before. And really, autosave is about saving automatically at key times.

and it frees the user from worry of data loss and relieves them of the manual and repetitive task of hitting Command-S or choosing File, Save over and over again, or in some cases, the lack of doing that manual or repetitive task, which can lead to the second bullet there. And the model we want you to think about is that the document on disk is the same as the document that the user sees on the screen.

We wanted to make autosave 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 autosaves 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 autosave.

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, you know, we typically think of that as being different than the document on disk.

until you explicitly save the document, either by doing Command + S or File Save. We indicate this state to the user with the dirty dot that's up here in the close 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 autosaves 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 that 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 autosave 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, NSDocument will save at key times and trigger an autosave 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 autosave 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 line, we still have the autosave 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 autosave. You just call the save methods with that autosave 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 implement it all for you. That means that we will autosave 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 autosave 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 autosave user experience is to spend less time saving. So with autosave 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 autosave 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 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. And a file wrapper helps you with that problem.

So there's another method to avoid interrupting the user, and that's what we call a cancelable autosave. So to explain what it is, first let me tell you what a normal autosave 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 prevent the document from being saved. But the other kind of autosave is an implicitly cancelable autosave. And right now in Lion, we trigger is only based on the autosave 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 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 by creating a new error, NSError -- excuse me, error with domain, NS_COCO_ERROR_DOMAIN and the NS_USER_CANCELED_ERROR and then return no from this method. It's important to use the COCO_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 autosaving 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 autosaving, let's look at what happens during a synchronous autosave. 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.

until the save is finished before they can edit the document again. Now, of course, the wait is important. We're doing real work then. We're preparing for the save. We're writing those contents to disk. We are doing the finish step, which includes the safe save feature. we can do better, and that's what asynchronous autosaving is all about.

So in this case, of course, the user will still edit, and that timer will still fire. At that time, however, we're going to split off a background thread, and the user only has to wait for as long as it takes the background thread to prepare the autosave. After that, the background thread allows the main thread to continue so the user can continue editing or interacting with their document while the write and finish steps happen on the background thread and do not block the user. program. 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 network file systems or slow hard disks. So we can make a-- or if the file is very large. So we can have a huge win by adopting this.

So when we introduce asynchrony into autosaving, there are a couple of concerns. The first is that there are two categories of resources that, so to speak, that need to be serialized. The first is the user. So 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 use it -- 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.

So let's see how you might use those. Here is an example of asynchronous file access. Now let's say that 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. Now 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'll 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're 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 NSDocument 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 a synchronous 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, you know, perform selector on thread or NS operation 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 NSDocument 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 autosave 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 it's thread safe. So all we need to do to enable asynchronous saving is-- 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 weight 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. And 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 NSURL's 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 gonna end up with a deadlock.

Now 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 NS Operation Queue or any of those technologies, we're going to use NS documents, 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 unpredictable blocking. And we also 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.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 autosave happens, while the user is in the middle of interacting with the document, they don't have to wait suddenly for that autosave 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 it 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. it.

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 autosaving in place, but like autosave, 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 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 version browser enters and exits. We have NSWindow will enter version 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, then 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 NSWindowDelegate method. Window will resize for version browser with max preferred size, max allowed size. So version size is this two-up user interface. And to show two documents at a time, we would show two documents at the max preferred size. And we also have a max 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 max allowed size is the maximum size of one in this document 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 line, 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 just 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 isDiscardable, yes. And NSDocument takes care of all the rest for you. If it's not an undoable change, you can use NSDocument's updateChangeCount mechanism. You just bitwise or the change you normally would have done, usually NSChangeDone, 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, addVersionInvitamentUrl 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 autosaving 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 diversions, 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 max preferred size, max 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 NSWindowAPIs to determine the proper optimal size for our document. So let me show you how these work.

versions here, 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'd expect. So these are just some very simple ways to use these APIs. There's 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 toolbars 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. NSDocument 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 API 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 destination URL 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'll 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. Amen.

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.

Welcome to WWDC. So now let me switch to another machine. OK, 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, add 2011. 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 is ubiquitous item at URL on an as file manager. And if it is ubiquitous, then we can change the title to remove from cloud instead of move to cloud. And we also set some state on the menu items 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 managers, 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, this won't be necessary, 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-app-delegate class. When the application finishes launching, we're gonna call start updating cloud document menu, which creates an NS metadata query using the new ubiquitous document scope. This causes NS Metadata again to search the cloud instead of your local file system for files. that match the predicate that you have set on the query. In this case, we're just gonna get basically all files.

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. Thank you. 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 wanna help you make your document-based application a first-class Lion Citizen. That means adopting autosave. And again, here's the one method you need to take away from this talk. Autosaves 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 autosave by saving fast and adopting asynchronous saving. We also talked about versions. And remember, it's on by default when you enable autosaving, but you can polish it too by streamlining the interface and browsing mode and allowing discardable changes. And then finally, we looked at iCloud. 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 Dudney, 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, you know, 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 autosave versions and iCloud. Thank you.