Document Architecture

This article begins by describing the Model-View-Controller (MVC) design pattern because this pattern informs application design that is most supportive of scripting, document-based applications, and undo. It does not fully describe the MVC design pattern in any formal way, because that’s not really its purpose, but it does discuss the pattern enough to give some background for the remaining discussion. More information about MVC is presented in the Cocoa Fundamentals Guide. In addition, Cocoa Bindings Programming Topics explains how you can use Cocoa bindings technology to keep model and view values synchronized.

The document architecture in the Application Kit is based on three classes: NSDocument, NSWindowController, and NSDocumentController. NSDocument is the principal class. It represents a single document in your application. Developers must subclass NSDocument to give it knowledge of the application’s model layer and to implement persistence (loading and saving). NSWindowController objects own and control the application’s user interface. An NSDocument object has one or more NSWindowController objects. Developers often subclass NSWindowController to add specific knowledge of the view layer that the controller is responsible for managing. NSDocumentController is a singleton class. Each document-based application has a single instance of NSDocumentController to track and manage all open documents. Developers typically don’t need to subclass NSDocumentController.

You can find more information about the document architecture in the Document-Based Applications Overview.

NSDocuments Are Model-Controllers

NSDocument is a model-controller class. Its main job is to own and manage the model objects that make up a document and to provide a way of saving those objects to a file and reloading them later. Any and all objects that are part of the persistent state of a document should be considered part of that document’s model. Sometimes the NSDocument object itself has some data that would be considered part of the model. For example, the Sketch example application has a subclass of NSDocument named SKTDrawDocument; objects of this class might have an array of SKTGraphic objects that comprises the model of the document. In addition to the actual SKTGraphic objects, the SKTDrawDocument object contains some data that should technically be considered part of the model because the order of the graphics within the document’s array matters in determining the front-to-back ordering of the SKTGraphic objects.

An NSDocument object should not contain or require the presence of any objects that are specific to the application’s user interface. Although a document can own and manage NSWindowController objects—which present the document visually and allow the user to edit it—it should not depend on these objects being there. For example, it might be desirable to have a document open in your application without having it visually displayed. For instance, a script might have opened a document to do some processing on it. If the script does not need the user to become involved in the processing, the script might want the document to be opened, manipulated, saved, and closed again, without it ever appearing onscreen.

NSWindowControllers Are View-Controllers

NSWindowController is a view-controller class. Its main job is to own and manage the view objects that are used to display and edit a document. A document that is visible to the user has one or more NSWindowController objects to own and manage the visual presentation. Although you can use an NSWindowController instance, most often you must subclass NSWindowController to add specific knowledge of the interface. An NSWindowController object usually gets its interface from a nib file. Subclasses often add outlets and actions for the controls and views within the nib file and the NSWindowController object usually acts as the file’s owner for the nib.

In very simple cases where there is only one window for a document, you may want your NSDocument class to have outlets and actions for the nib. In this case, the NSDocument subclass acts as the file’s owner for the nib, but it still creates an NSWindowController instance to own and manage the objects that are loaded from the nib. If you do choose to adopt this approach when quickly prototyping an application, you should be careful to localize the portions of your code that deal with the user interface, so you can later extract them from the document and put them into a custom window controller as your application becomes more complex.

Type Information and NSDocumentController

An NSDocumentController object manages documents. It keeps track of all open documents; it knows how to create new documents and how to open existing documents. It knows how to find open documents given either a window whose window controller refers to the document or the path of the file the document was loaded from. Developers typically don’t have to worry about what it does. NSDocumentController knows how to read and use the metadata that a document-based application provides about the types of documents it can open. NSDocumentController can provide information based on that metadata, such as lists of file types supported by an application and which NSDocument subclasses are used for them.

All document-based applications declare information about the document types they support in the information property list (named Info.plist by default) of the application. Xcode provides an editor for creating and modifying this metadata.

When the NSDocumentController object creates a new document or opens an existing document, it searches the property list for such items as the document class that handles a document type, the uniform type identifier (UTI) for the type, and whether the application can edit or only view the type. Similarly, Launch Services may use information about the icon file for the type. Supplying this information in a property list enables an application to support the Open and Save panels with little effort.

For details on the Info.plist keys required by the document architecture and how to include this metadata in your application project, see Storing Document Types Information in the Application's Property List.

Typical Usage Patterns

You can use the document architecture in three general ways. The following discussion starts with the simplest and proceeds to the most complex.

The simplest way to use the document architecture is appropriate for documents that have only one window and are simple enough that there isn’t much benefit in splitting the controller layer into a model-controller and a view-controller. In this case, the developer needs only to create a subclass of NSDocument. The NSDocument subclass provides storage for the model and the ability to load and save document data. It also has any outlets and actions required for the user interface. It overrides windowNibName to return the nib file name used for documents of this type. NSDocument automatically creates an NSWindowController instance to manage that nib file, but the NSDocument object itself serves as the File’s owner of nib file.

If your document has only one window, but it is complex enough that you’d like to split up some of the logic in the controller layer, you can subclass NSWindowController as well as NSDocument. In this case, any outlets and actions and other behavior that is specific to the management of the user interface goes into the NSWindowController subclass. Your NSDocument subclass must override makeWindowControllers instead of windowNibName. The makeWindowControllers method should create an instance of your NSWindowController subclass and add it to the list of managed window controllers with addWindowController:. The NSWindowController should be the File’s owner for the nib file because this creates better separation between the view-related logic and the model-related logic. This approach is recommended for all but the most simple cases.

If your document requires (or allows) multiple windows on a single document you should subclass NSWindowController as well as NSDocument. In your NSDocument subclass you override makeWindowControllers just as in the second procedure described above. However, in this case you might create more than one instance of NSWindowController, possibly from different subclasses of NSWindowController. Some applications need several different windows to represent one document. Therefore you probably need several different subclasses of NSWindowController and you must create one of each in makeWindowControllers. Some applications need only one window for a document but want to allow the user to create several copies of the window for a single document (sometimes this is called a multiple-view document) so that the user can have each window scrolled to a different position, or displayed in different ways. In this case, your makeWindowControllers may only create one NSWindowController instance, but there will be a menu command or similar control that allows the user to create others.

Documents and Scripting

Scripting support is mostly automatic for applications based on the document architecture, for several reasons. First, NSDocument and the other classes in the document architecture directly implement the standard document scripting class (as expected by AppleScript) and automatically support many of the scripting commands that apply to documents. Second, because the document architecture is intended to work with application designs that use MVC separation, and because scripting support depends on many of the same design points, applications that use the document architecture are already in better shape to support scripting than other applications. Finally, the document plays an important role in the scripting API of most applications; NSDocument knows how to fill that role and provides a good starting point for allowing scripted access to the model layer of your application.

If an application is not based on the document architecture, making it scriptable requires that you duplicate work you would otherwise get for free. See the Sketch example project for an example of how to implement a scriptable NSDocument-based application.