App Frameworks • OS X • 49:51
Lion introduces Resume: a simpler way for apps to exit and later resume where they left off. Come learn how to take advantage of new APIs to enable your users to spend less time managing your app and more time using it. You will learn about the Resume feature as we walk through converting real applications to adopt automatic termination and window restoration. The session will include plenty of concrete code samples and practical advice.
Speakers: Peter Ammon, David Smith
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Welcome to session 119, Resume and Automatic Termination in Mac OS X Lion. My name's Peter Ammon. I'm an engineer on the Cocoa Frameworks team. We're talking about Resume and Automatic Termination. So Lion is about bringing features from iOS forward to the Mac. And one of the features that users love on iOS is how apps pick up where they left off.
When you switch to an app, it restores its state. And the app may or may not have terminated. The underlying Unix process may or may not have exited in the time between you switching away and switching back to the app. Users don't know and they don't care. This is a much simpler application model than we have on Snow Leopard where users must manually quit and relaunch applications. So Mac OS X Lion is moving in that same direction. And two ways in which we're doing that are called Resume and Automatic Termination.
So I'll start by talking about Resume. We'll go over the API. We'll see a bunch of demos. And then in the second portion, I'll bring out my colleague, David Smith, who will talk about Automatic Termination and show you a demo of that. So, Resume. As you may have guessed, the point of Resume is that apps simply resume where they left off. and they do this after quit, logout or a crash. And I'm going to show you a demo of that last one right now.
This is a Mac OS X Lion. I'll start by launching a few applications. There's a text-edited app with a few documents open. We'll open a picture of a lion. Put that there. I'll open Safari. Maybe a few Finder windows. Let me make this a little smaller. So what I'm going to do is send sigkill to login window.
And that's going to effectively crash login window and by extension all the applications including finder text that you see here. And if all goes well, what you will see is that the screen will go a little wonky for a few seconds and then all the applications will come back as if I had never left. So let's give it a try.
Isn't that cool? So let's be clear about what just happened. All the processes died. They all restored their state when they were relaunched, and they did it just using the APIs that I'll show you today. And to prove that, I'm going to turn on private browsing in Safari.
Now, when private browsing is on, of course, the application doesn't-- The windows are not written to disk. No state about them is recorded, not in the bookmarks or history or any of that, including in Resume. So when I repeat this exercise, notice by the way it remembered the last command I did, which was kill all. When I repeat this, I expect to see every window come back except Safari. And there it is.
Back to slides. And by the way, that's not an SSD machine or anything. That's a normal spinning hard drive. Why Resume? Why are we doing this? As I said before, it's about simplifying the application model. Users are going to come to expect that applications don't lose their state when they quit or when the applications crash or when they restart.
And eventually we'd like to get to the point where your Mac will be able to silently install an update overnight when you're asleep and then reboot and all the state will be restored exactly as it was and the user won't even know that the update has been installed aside from all the nice bug fixes.
So you might be thinking, "Well, that's pretty nice, but I can do this on my own. Why should I use the Cocoa APIs to do this?" Well, one answer is, if you think about it, there's an awful lot of state to restore. A window has a window frame on a particular display, on a particular space, and maybe the window's minimized, maybe it's full screen. It's a lot of state to keep in mind.
And also by using Resume, you integrate with the rest of the system. For example, the inter-application Z-order was preserved for these windows after the crash. Another example of that is if you hold down the Shift key when you launch an application, it will not restore state. That's a power user feature. And by using Resume, you pick those up automatically.
And lastly, it's really easy. Incremental adoption is very possible, meaning you don't have to throw a big switch. You can make more and more of your windows restorable. And it complements existing persistence mechanisms. So maybe you already know how to restore state. That's great. You have a huge head start. You don't have to throw any of that away. You can integrate your existing restoration into Resume. So let's give an overview of the API.
This feature is inspired by iOS, but the Mac has many challenges that are not present on iOS. For example, Mac applications often have multiple windows, and those windows can reference documents, which may be on a network share or an external hard drive. In general, there's just a lot more states that applications can get into.
In fact, there's additional state from plugins and frameworks. For example, the font panel has its own state. So here's how we're meeting those challenges. So this is the lifecycle of a window under Resume. Initially, some restorable window is open, in this case a Pages document. Cocoa will notice that this window is open and it will ask the window to encode its state.
When the user quits or logs out, or crashes of course, and then relaunches the app, Cocoa will notice, "Hey, this application had a window that had a restorable state that was open on the last time the app was run." And it's going to ask the component responsible for that window to provide that window again.
So the component will provide the window, possibly creating it or possibly returning an existing window. Cocoa will then take that window and apply the state that was saved on the last run to the window. So you see the window goes from blank to being filled with content. And once again, we're back where we started. We have a restorable window that's open.
So as you can see, the Resume API is centered around Windows, although there is support for state attached to NS application as well for global state. And every component can take responsibility for its own Windows. So AppKit can take responsibility for restoring the font panel or color panel. Your application can restore the Windows it knows about. If your application has plugins, they can restore their own Windows, etc.
As we saw, there's two phases. There's recreating the windows that were open, and then there's restoring the view state within those windows. So here's phase one. Here's the API for recreating windows. To mark a window as this is a window that I would like to restore, you just simply call setRestorableYes. In fact, this is on by default for any window which has a title bar, a titled window, and it's off for windows that don't, borderless windows.
Next, you set the Restoration class. This is the class that's responsible for recreating the window when the application is next launched. This is what I meant by each component can take responsibility by component as reflected as a class in the API. And lastly, the class implements the Restore method. This is the method that gets called when the application is relaunched and Cocoa wants to restore one of the windows that was open.
So let's zoom in on this method. The responsibility of the class is to invoke the completion handler, that third parameter that takes an NSWindow and an NSError. It's going to invoke that with the corresponding window. Now, the identifier parameter is just a way to distinguish between several windows.
For example, if your class restores a few well-known windows, it can distinguish them by setting an identifier on the window to determine which window to restore. And this is set up, by the way, in Interface Builder, the Interface Builder part of Xcode. The state parameter can be used to track even more information, for example a document URL. We'll see more of that in a bit.
Now, so for example, here's how AppKit restores the font panel. It starts by just marking the font panel as Restorable. It sets a Restoration class to the FontPanel class itself. Why not? and it implements this restoreWindowWithIdentifier method and all it does is immediately invoke the completion callback with self-shared font panel. Now note this window may have already been created. It's perfectly fine to pass a window that's already been created to the completion callback.
will also be possible to copy the completion handler and invoke it at some point in the future. If you have an NSDocument-based application, there's some extra integration that goes on with NSDocument. NSDocumentController will set the restoration class of all your document windows to the DocumentController itself. and then it will reopen the windows by first reopening the documents and then attempting to derive the windows associated with the documents from the document. This is a very customizable process. One way to customize it is to simply override the method I illustrated earlier, restoreWindowWithIdentifier on the document controller. So this would be invoked when it's time to reopen a window associated with a document.
Or if you don't want to take responsibility for reopening the document, but you do want to control how windows are derived, you can override Restore Document Window with Identifier. And I'm going to give you a demo of this entire process now. Let's get rid of all this. Oops. Command-option-Q there.
So I'm going to start with base Sketch. This is Sketch as it's shipped in Snow Leopard, and we'll see how to make its windows restorable progressively. So if I just run Sketch-- If I run Sketch, it's going to open an untitled document. And I can, of course, add some shapes, drawing to it.
And then when I quit, it's going to ask me if I want to save it or not because Sketch has not adopted autosave. So if I don't save it, And then I relaunch Sketch. It doesn't come back because the document wasn't saved. I repeat the process, but this time I'm going to choose to save it when I quit.
My Sketch document. And then I relaunch it. It will restore the document. So Sketch hasn't done anything to adopt. The reason the document's coming back is because of the NSDocument integration that I talked about earlier. And by the way, any time you want to test Resume from Xcode, you have to be sure that it's enabled. If you go to edit your scheme and you go to the options of the run step, you'll see a disable state restoration checkbox. That's a reference to Resume. You want to make sure it's unchecked and then you can test Resume. I think it's unchecked by default.
I'm going to quit Sketch. So let's go to a version of Sketch in which I've enabled autosave. And this is all I've done. All I've done is override autosaves in place on the Sketch document. Hopefully you saw the autosave inversion session earlier. When I run Sketch with this-- You can see it brings back the document, and this time if I make an untitled document and I'll add a shape to it, I'll minimize this one for fun.
Now when I quit, it brings back, you can see, the minimized document but also my untitled document. So previously it would have warned me. Now it just brings it back. That illustrates how autosave makes for a much better user experience, especially with Resume. So let's say I open the Inspector window and the Grid Options window. And I quit Sketch and I run it again. These windows aren't brought back, so let's make these windows restorable.
So, these windows, of course, are set to be set by default because they are titled windows. So we have to set a restoration class. And you'll see the restoration class for each of these windows is just the self class, that's the SKT app delegate class. Now, in the RestoreWindowWithIdentifier method, which I've implemented, we start by getting the actual application delegate because this is a class method.
And because we only have these two global windows, we can distinguish them using the identifier. So we're going to say if the identifier is equal to Inspector, then we're going to get the Graphics Inspector and get its window. Otherwise, if it's equal to Grid, we can get the window.
Otherwise, it's a window we don't know about, but we want to make sure that we still invoke the completion handler with nil. This would come about, for example, if I ran an earlier version of Sketch with save state from a future version. And by the way, these identifiers here, I simply set in the, here's the window. You can see in the class properties, or the object properties, that there's an identifier field and that's where that grid and inspector identifier came from.
So that wasn't a whole lot of work. Now when I run Sketch and I show the inspector and I show the grid options and I quit and I run it again, these come back. And if they were on another, you know, their frame is restored, if they're minimized, et cetera, that would all be, that state would all get restored automatically by NSWindow.
So there's a feature of Sketch. If I hold down Option, I can make a new window for a document. And you can see that the two windows reflect the same data. If I edit one, it appears in the other. But if I quit, and I relaunch. Only one window is brought back.
This is of course because NSDocument doesn't know how to create multiple windows and the document by default only creates the one. So this is an excellent candidate for one of the methods I talked about, the restore document window. This is called when the document has already been reopened, but you want to derive a window from it.
So here's all I've done here is restored-- I've implemented a restored document window with identifier state completion handler on the SKT document. If I wanted to, I could call through to Super, but in this case, I'm just going to create a window controller, add the window controller, invoke the completion handler, and release the window controller.
This is essentially the same thing that happens when you choose new document window from the menu, except, of course, for the completion handler. So, with this method implemented, I can create a new window. And I quit. And I relaunch it. And it opens both the windows. So you can see how we've sort of incrementally adding Resume functionality to Sketch.
So we took windows that were not restorable and made them restorable. But which windows should be restorable? Well, most windows should be, but there are some exceptions. For example, if your window is very transient, like it's a tool tip or it's a shielding window, those don't need to be restorable, like a menu or a pop-up, something like that. If the user does not want to restore the window and they've indicated that, of course then it should not be restorable either. We saw that with the private browsing example in the first demo.
If the window's job is done in some way, for example, when you install an application and you get a sort of window which just says "Install Complete," that window doesn't need to be restorable because it's no longer very interesting. And if your app cannot restore a window, it's good practice to mark the window as not restorable just so that there's no confusion. Hopefully you can make those restorable in the future.
So now that we've got our windows back on screen, how do we restore state within those windows? So every component within the window has its own private store of state, which is an NSCoder. And by component, I mean every view within the window, the window itself, its window controller, and the document associated with the window controller. And that's true for NSApplication. It also has some state that it can restore.
Now, when the state gets dirty, it invalidates its state by invoking self-invalidateRestorableState. This is sort of like setNeedsDisplayYes. It indicates I need to have my state written to disk to be recorded. And this is a very inexpensive call. It's very fast because it doesn't actually do any work initially. It just marks that particular component as being dirty. Now, at some point in the future, though, that component, the view, for example, will be asked to encode its state.
This is a method encodeRestorableStateWithCoder will be invoked. This is a method you would override to associate state with a view. This typically happens when the app is deactivated or on a timer after the last call of invalidateRestorableState. And then when the application is relaunched, each component will be passed its state from the last run via the method restoreStateWithCoder. So the state that was encoded in encodeRestorableState is now available in decodeStateWithCoder, or excuse me, restoreStateWithCoder.
There's two useful delegate methods on NSWindow. If all you want to do is just associate a little bit of state with a window without-- but you don't want to make an entire subclass, you can implement the delegate method. Window will encode RestorableState, and Window did decode RestorableState. And as a delegate, you can encode your own RestorableState into that-- into the Windows NSCoder.
and furthermore, if you have some properties which are KVO compliant, you can override the class method, Restorable State Key Paths. What this will do is Cocoa will observe all these key paths and notice when they change. When they change, it will encode the new value to disk and when the app is restored, it will automatically use KVC to set those properties back on each component. This is a really easy one-line way to get some state persistent and restorable.
So this is a very general mechanism, but what state is appropriate for this mechanism? Here's a text edit window, for example, and you want to think about this state as being associated with the view and controller layers of the MVC model. So for example, restoring the selected range or the scroll position of the text document.
And by the way, NSTextView will do both of those for you. But you would not want to restore, for example, the actual text of the document. That's part of the model state, and that should come from typically a file on disk, for example, or whatever your model is.
This can cause an interesting challenge because the view and model state may get out of sync. For example, if while text edit is quit, I go into an Emacs and I change the file, when I restore the window, it's going to have a different text than it had before. And if you're not careful, it can cause a problem.
For example, at one point early in Lion, the selected range would get restored, and if it happened to be beyond the new text, it would throw an exception because we tried to restore a range that was too long. So you need to be sure to validate your state. All right, I'm going to give a demo of how we can make sketches window state-restorable. So I need to switch to this thing.
So you'll notice here if I zoom in on this document, the 200, and maybe I'll scroll a bit, and I quit, Can I relaunch? It's taking me back to 100, so the zoom is not restored properly. So this would be a -- let's see how we do that.
Now, the Resume property, turns out this is a KVO and KVC compliant property with a keypath factor. So to make this restorable, all I need to do is override RestorableStateKeypaths, invoke super. This is on the scroll view itself, by the way. Each view can take responsibility for its own state.
I invoke super-restorable-state keypaths, and I just add the object, my keypath for the zoom. That's all I have to do. Now when I run Sketch, I zoom in and I'll scroll a bit and I quit. So now that zoom is saved in my persistent state. When I relaunch, it's now zoomed to 200% and you'll notice also that the scroll position was restored. That's in a scroll view encoding and restoring its own scroll position state.
So that was a really easy way if you have a KVO or KVC compliant property to make that restorable. Now if I select a few of these figures here and I quit, And I relaunch. You'll notice that the selection isn't restored. So let's make this sketch save and restore its selection.
So because the selection is a little more complicated, we're going to use the EncodeRestorableState and Decode-- excuse me, RestoreStateWithCoder method to persist this state. On the SKT graphic view, which is the view that draws the graphics and tracks the selection, I'm going to override EncodeRestorableStateWithCoder. I start by invoking super in case a super class has any state it wants to restore. And then I just call selfEncodeObject SelectionIndexes. This is an NSIndexSet of the graphics that are selected, and I pass it the key SelectionIndexes. I can use any key I please.
Now in the restoreStateWithCoder method, I'm going to first call super restoreStateWithCoder so that the superclass can restore its own state. I'm going to decode the selection indexes, and if I have them-- now, I might not have them if I ran an earlier version of Sketch, so I want to validate it. I check it. It's not nil. Now I need to make sure that the index set is not out of bounds because, as I mentioned, the document contents may have changed independently from the Restorable State.
So here's a way of just validating that every index is no larger than the number of graphics I have. Once I've done that, I simply call self-change selection indexes with the validated selection indexes. This is the funnel point that all graphic selection changes go through. And the third piece of the puzzle is whenever that gets called, whenever the user changes the selection, I need to Invalidate the Restorable State. So here we are.
In Change Selection Indexes, all I'm going to do is add a line, Self-Invalidate Restorable State, which will ensure that my state for this view is encoded before the application quits. So now if I run and I'll select a few figures here, and I quit, and I run it again, if I get this out of the way, we'll see that it restored the selection.
Switch back to slides. And again, this is a very incremental process. We just keep adding more and more state that gets restored. So Sketch is a simple application. What if your app is more complicated? For example, if your application wants to restore a window associated with a document, it may need to know the document's URL before it can even create the window. So this brings me back to that state parameter in the first part of my talk, Restore Window with Identifier. That state parameter is a collection, it's a union of the Restorable State of the window, the window controller, and the document.
It's a combination of all of them. So this allows you to get access to the Windows private state before the Windows has even been created. Now if you, in reading this state, discover that maybe the document's been deleted, you want to be sure to invoke the completion handler anyway with nil. If you don't invoke the completion handler, the app will appear unresponsive until you do. So always invoke it.
So let's go over some more advanced topics and best practices for an app living in a Lion Resume world. So here's how NSWindow encodes its first responder, which is the view that has the keyboard focus. It just does it. It calls encodeFirstResponder for some key. Now this may seem dangerous, right? Is this going to archive an entire view hierarchy? Well, in fact, it doesn't. Encoding an NSResponder just does not call, um, not actually archive that responder. Instead, it archives a reference to it. And when you decode it, it's going to return a responder that's already in the window.
So when you encode your first responder, Then you decode it, you're going to get the view that represents whatever view had the keyboard focus in the previous run. and in fact references can even cross windows. For example, the color and font panel, they encode their target which is typically in another window entirely. When the state is restored, they can decode their target and that just works.
Sandboxed apps. So you may be worried, "Hey, my application has a file open. If I relaunch my application, how am I going to be able to get permission to reopen this file?" The secret here is to encode a URL to the file in the restorable state and any NS code in the restorable state, so in the view or in a window or in NS application itself.
URL is special in that it will automatically give you permission to reopen this file when your application is next launched. This is true even if the file has been moved or renamed because URL encoding uses bookmarks. You have to encode the NSURL itself. You can't create your own bookmark as an NSData and encode that because it will not be recognized as an NSURL and you will not get permission to reopen that file. or of course if you're NS Document based, NS Document will take care of this for you without you having to do any work.
So I alluded earlier to the fact that if you already have a mechanism for restoring your state, it's very easy to integrate with Resume. And I'd like to share how we did that with Mail. Mail, of course, has an NSUserDefaults state restoration mechanism. It writes its window state to user defaults.
and integrating Resume was only about 60 lines of code. It was not difficult at all. So here's the basic idea of how it was done. We give each window a unique ID, for example, a UUID or just an auto-incrementing integer. And we record that UID in both the Restorable State and NSUser defaults, its existing persistent state mechanism.
Then when the application is relaunched, Mail restores its windows via NSUser defaults like it's always done. And then it goes through each of the Resume callbacks that it got and decodes that UID from the Restorable State. It locates a window that's already recreated and then invokes the completion handler with that window or nil if it couldn't find it. So the UUID acts as kind of glue between the Restorable State mechanism and its existing NSUser defaults restoration.
Here are some best practices. When your app is launched, don't assume that the user is going to use it right away. For example, in my crash demo, you saw that all these applications were relaunched. And if they started opening new windows or throwing up dialogues, it would have been a bad user experience. Your app can be relaunched in the background, so keep splash screens, etc. They'll be perceived as annoying, so keep those to a minimum.
Don't create windows an application did finish launching. Your app may already have windows that have been restored. If you do create a window here, it can lead to what we call the N+1 effect, where you launch an application, it creates a window, you quit, you relaunch it, it restores the first window, creates a new one, so now you have two, three, four, etc.
That's not good. So what you want to do instead is prefer application open untitled file, the delegate method. This is called at the right time when either the app is launched in the foreground and there's no windows or the user clicks on the dock, etc. Now the exception to this is if your app doesn't even make sense without a particular window, like iPhoto, iPhoto has just one window, you can always create that window anytime you like.
Be prepared for unexpected changes. So when your app is relaunched, things may-- state may have changed between when it was run before and how it's running now. The screen size may have changed. The user may have toggled. The scroll bar preferences-- the file contents may have changed because of iCloud or other reasons. So you always want to validate that your saved state still makes sense. And don't forget versioning, so you maybe have saved state from a previous or future version of your application that you want to be able to version that correctly.
and partial state restoration is okay. Though higher fidelity is obviously better. As you saw with Sketch, we incrementally added more and more Resume capability to the app, but there's still more we can do. We're never going to be able to restore everything exactly, so it's okay to do a very good job.
But the more you use Cocoa, the less work it's going to take. For example, if your application uses Fullscreen, if you use the Fullscreen APIs we're introducing in Lion, it will be automatically restored to Fullscreen when the user quits and relaunches the app. But if you roll your own Fullscreen implementation, which you can do, you'll have to do your own work to integrate with Resume.
So here's a summary of Resume. Resume is, of course, about restoring state. Cocoa tracks the windows that are open and asks those windows to encode their restorable state and then writes that to disk. Each component can take responsibility for the windows it knows about and can create. And each NSResponder, each view for example, also can take responsibility for its own state. So with that, I'm going to bring up my colleague David Smith, who will tell you about Automatic Termination in Lion.
Thank you, Peter. Good morning, everyone. I'm David Smith. Like Peter, I'm an engineer on the Cocoa Frameworks team. And I'm here to talk to you about automatic termination. This is part of the triad of features, autosave, resume, and automatic termination, that all go together to simplify the application model in Lion. It's a new technology. So first, I'm going to talk about what exactly it's doing, give you a brief overview of it. Then I'm going to talk about some benefits that it brings and why you might want to adopt it in your app.
I'm going to give you an overview of the API and how you can adopt it in your app. And then I'll talk briefly about some future directions that we might take this feature. So what is automatic termination? First and foremost-- It's a new user model. It lets users focus on using apps instead of managing them. So instead of having to quit applications and relaunch them, instead of having to manage memory themselves, when they run out of memory, quit an app to free it, et cetera, they can just focus on getting their work done.
It does this by decoupling the concept of apps and processes. In pretty much every historical system, an app has always had a process associated with it, and a user process has always had an app associated with it. Neither of these is true, necessarily, in Lion. I'll go into more detail about how both of those happen later on in this presentation.
So this is the application lifecycle in the Stone Age, AKA Snow Leopard. Users have to spend time quitting applications, launching applications. They have to keep track of what's open and what's not open. We'd like to change that, focus on just using it and letting the system manage these sort of extra duties. You can think of it as sort of like garbage collection for processes.
So, there are a couple of side benefits that we get by doing this. We also get to bring iOS-style memory reclamation to the Mac. When the system's running out of RAM, it can terminate unneeded processes to free them up. And on the flip side, if it doesn't need to terminate processes, it can keep them around after the users quit them, which gives you effectively instant relaunch.
So I'm going to go into a little bit more detail about why you would want automatic termination for your application. What's in it for me, basically. First and foremost, it's about playing well with others. If your application is a good citizen on the system, then all the other applications on the system are going to perform well.
And on the flip side, if all the other applications are good citizens, then your application is going to perform well. So it's in everybody's best interest to play well with others. It's also going to meet the user expectations for new applications. We're pushing this pretty heavily. We'd like it to be the new way things work. And applications that don't do it are going to start looking old.
Instant Relaunch is another side benefit. It doesn't happen in all that many situations in Lion, but it could happen more in the future. And even in the situations it does happen in Lion, it's very nice. And finally, as I've alluded to, this gets you ready for possible future plans, which I'll talk about near the end of this presentation.
So at this point, hopefully I've sold you on the basic concept of automatic termination. So we're going to look into a little bit more depth of how it works and how to adopt it in your application. The API is very simple. We modeled it after the Sudden Termination API that was introduced in Snow Leopard, which brings me to a very important aside.
Sudden Termination and Automatic Termination are not the same thing. Pretty much every time I've mentioned this to somebody, they're like, "Oh, I think I remember that." No, you don't. This is new. Sudden Termination is about quitting very quickly without doing a lot of work or touching much memory.
Automatic Termination is the new UI model and the new process model. We would, of course, like you to participate in both. That makes it so that when the system terminates your application, it can do things like terminate it without touching a lot of memory pages, which might induce more paging activity. It can also terminate it very quickly without getting in the user's way or slowing down the foreground app. But if for some reason you can't adopt both, you can do one or the other or neither. They're completely independent.
So the first thing you need to do is turn Automatic Termination on. You can do this in one of two ways. You can set a property list key in your Info.plist, and it supports Automatic Termination. And that's a Boolean property, so you just set it to true, and we'll take it from there. Alternatively, if for some reason you can't do that, you can use the NSProcessInfo API, set Automatic Termination enabled, and just pass yes to that. If you pass no, it won't do anything. Don't do that.
Once you've done that, this is where it gets There we go. This is where it gets very slightly more complicated. You need to identify each place in your app where you're doing an activity. And I realize that's a somewhat fuzzy term, but basically the way to think about it is this.
If your application were to quit right now, would that break something the user was doing? So, for example, in iChat's case, If there's an open connection to the IM server, you don't want to quit. That would terminate your connection. You'd appear as offline to people. That's not very good. In, say, Photoshop's case, if you have an extremely long-running filter going, you don't want to terminate in the middle of that.
In general, any application that has a long task running on the user's behalf should do this. And what you do is just before doing that, you call disableAutomaticTermination on NSProcessInfo. And you can pass a reason argument here, which is a string. That doesn't actually do anything. It's just a hook for possible debugging tools like DTrace, which will help you match up your calls to disableAutomaticTermination with future calls to enableAutomaticTermination to make sure you don't get those unbalanced.
Because the next thing you need to do is once you've finished doing your work on behalf of the user, you return control to the system by calling enableAutomaticTermination, again on NSProcessInfo. So I mentioned before that there are two types of automatic termination. You can have processes with no application associated with them, and you can also have applications with no process associated with them.
These happen in related but slightly different situations. If your application has open windows that are not visible, all those open windows are restorable. Peter just showed you how to do that. You're not the active application. You haven't disabled Automatic Termination. And the system is running out of available memory. Then the system is free to terminate your process while leaving your application appearing to still run.
If blue dots are on in the dock, it will still show up with a blue dot there. If your application isn't pinned to the dock, it'll still show up there. It'll appear in Command Tab. As far as the user can tell, it's still running normally. When they click on it or switch back to it, and David Smith will be here to help you.
The system will relaunch it transparently and all they will see is a slight lag as it comes back which is much, much better than the system going into VM paging and everything slowing down. On the flip side, you can have processes with no running application. This is where the new UI comes in. If your application has no open windows, if the user has closed all of their documents, it's not the active application, you haven't disabled automatic termination, and at least one window has ever been open, then your application will appear to quit.
This might seem a little bit unexpected. This is a new usability model that we're pushing. We've seen a ton of people switching from other systems, including iOS, that expect applications to not require an explicit quit. You may have seen people with every single app in their dock open and no windows and wondering why they're running out of RAM. At this point, the kernel will go through the list of processes and look for ones that are using a lot of RAM and send them a message asking them to terminate themselves quietly.
Sorry, that happens if the system runs out of memory after the application has appeared to quit. If the system doesn't run out of memory, then the process will continue sitting in the background quietly using no CPU, and if the user relaunches it, then it will just come back instantaneously, which is pretty cool. So I'm going to give you a brief demo where I convert Sketch over to Automatic Termination. My starting point here is the Resume-enabled version of Sketch that Peter left me.
I'm going to open Sketch, close its documents. That one's actually already Automatic Termination enabled. Well, So what you just saw there was automatic termination in action. I wasn't actually expecting that to happen quite yet, but that's fine. So if I go to Activity Monitor, you can see that Sketch is actually still running. And if I open Sketch now, it's going to come back instantly.
If I actually hit Command-Q, that's going to quit Sketch. But if I close the last window and switch away, that will leave it running. Until I ask the system to reclaim memory. So I'm going to do that by using a debugging tool that we have called TalAgent. That name may seem eerily familiar to those of you who have been here in decades past. It's unrelated to the previous one. And I pass the -memory_pressure argument to that. This is wrapping on a new line. Let me make that wider.
You can see that the Sketch process is still running, and when I trigger system memory pressure, Sketch disappears completely transparently. It freed all of its memory, and whatever process triggered the memory pressure is now free to use more memory. So I'm going to add the Info.plist key, and it supports automatic termination.
And that's a Boolean. So Boolean. and David Gass. That's literally all it took to adopt basic automatic termination in Sketch. However, I've added and David Smith. We will assume the user doesn't want their important work going away when I switch away. So what I'm going to need to do here is go to the file with my important work function, which is SKT AppDelegate.
Find my doBackgroundOperation method that I added here, which is just showing a progress panel. And around that, I'm going to add a call to NSProcessInfo. Disable Automatic Termination. And for the reason I'm going to put, doing important work. As I said before, the reason argument is really not important for functionality.
You just want it to be descriptive so when you're debugging, you can tell what the heck is going on. And that needs to go where we are starting to do work. And then when we're done doing work, I just add a call to re-enable Automatic Termination. And you want the reason argument that you passed to match.
So now if I build this and run Sketch and start doing background work, switch away, Sketch now still appears to be running. That is exactly what the user would expect in this case. Their important work was not lost. and if I then stop doing background work and switch away, Sketch disappears from the doc. So the key things that we've learned here, I'm going to switch back to the slides.
Opt-in: Enable automatic termination using the NSSupportsAutomaticTermination key. Then disable automatic termination around important work. And finally, test with the TalAgent tool. You can find that in System Library Core Services and you pass the -memory_pressure argument. I believe there's a man page for it if you forget. We really only want you to use this for testing.
I realize it's tempting to stick a utility out there for $10 that triggers system memory pressure as a cleanup tool or something. That really, really won't do anything and we don't guarantee that this tool is even going to exist in the future. It's just a debugging tool. But definitely test your application because you don't want it doing anything unexpected.
So to recap, enable automatic termination, disable and then enable around activity, test with Tal Agent. So I'm going to finish up here by doing a bit of blue sky talking. I realize we don't normally do that at WWDC, but I think there's some really exciting things that could be done with this. So I want to do a little thought experiment. I want you to imagine a world where there literally isn't a way for users to quit applications.
where applications go away entirely on their own when the user's done with them, and users don't really need to manage them. And as an extension of that, where there's no way to tell if an application is running. And that would also mean that a user has no reason to need to know that an application is running.
All they need to know is, does this application have open documents? Is this application doing work on my behalf? And that's a completely separate concept from, is it running? We can just eliminate this concept from the user's vocabulary and simplify the system in the process. And finally, I'd like you to imagine a world in which your parents never call you and say, my computer's slow, fix it.
I think we can do something like this. Lion is a big step in that direction. And if all of your applications are on board with Automatic Termination, then I think we can keep pressing forward in this direction. Thank you for coming to our talk on Resume and Automatic Termination, and hopefully you will adopt it in your applications.
You can get more information from Bill Dudney. He's our Applications Frameworks Evangelist. We have documentation on everything that we've talked about here on the developer.apple.com site. And you can come to our developer forums where we are indeed reading your posts and most of the time replying to them.
There's two related sessions which have already happened, so hopefully you went to those. There's What's New in Cocoa, where we discussed a number of things that are new in Cocoa, as well as Automatic Termination and Resume. And there's Autosave and Versions, which tie very closely into this. You noticed that Autosave allowed the Resume demo earlier to work automatically for documents that have titles.