Designing a Document-Based App
Documents are containers for user data that can be stored in files locally and in iCloud. In a document-based design, the app enables users to create and manage documents containing their data. One app typically handles multiple documents, each in its own window, and often displays more than one document at a time. For example, a word processor provides commands to create new documents, it presents an editing environment in which the user enters text and embeds graphics into the document, it saves the document data to disk or iCloud, and it provides other document-related commands, such as printing and version management. In Cocoa, the document-based app design is enabled by a subsystem called the document architecture, which is part of of the AppKit framework.
Documents in OS X
There are several ways to think of a document. Conceptually, a document is a container for a body of information that can be named and stored in a file. In this sense, the document is an object in memory that owns and manages the document data. To users, the document is their information—such as text and graphics formatted on a page. In the context of Cocoa, a document is an instance of a custom
NSDocument subclass that knows how to represent internally persistent data that it can display in a window. This document object knows how to read document data from a file and create an object graph in memory for the document data model. It also knows how to modify that data model consistently and write the document data back out to disk. So, the document object mediates between different representations of document data, as shown in Figure 1-1.
Using iCloud, documents can be shared automatically among a user’s computers and iOS devices. The system synchronizes changes to the document data without user intervention. See Storing Documents in iCloud for more information.
The Document Architecture Provides Many Capabilities for Free
The document-based style of app is one design choice among several that you should consider when you design your app. Other choices include single-window utility apps, such as Calculator, and library-style “shoebox” apps, such as iPhoto. It’s important to choose the basic app style early in the design process because development takes quite different paths depending on that choice. If it makes sense for your users to create multiple discrete sets of data, each of which they can edit in a graphical environment and store in files, then you should plan to develop a document-based app.
The Cocoa document architecture provides a framework for document-based apps to do the following things:
Create new documents. The first time the user chooses to save a new document, it presents a dialog in which the user names and saves the document in a disk file in a user-chosen location.
Open existing documents stored in files. A document-based app specifies the types of document files it can read and write, as well as read-only and write-only types. It can represent the data of different document types internally and display the data appropriately.
Automatically save documents. Document-based apps can adopt autosaving in place, and its documents are automatically saved at appropriate times so that the data the user sees on screen is effectively the same as that saved on disk. Saving is done safely, so that an interrupted save operation does not leave data inconsistent. To avoid automatic saving of inadvertent changes, old files are locked from editing until explicitly unlocked by the user.
Asynchronously read and write document data. Reading and writing are done asynchronously on a background thread, so that lengthy operations do not make the app’s user interface unresponsive. In addition, reads and writes are coordinated using the
NSFilePresenterprotocol and the
NSFileCoordinatorclass to reduce version conflicts.
Manage multiple versions of documents. Autosave creates versions at regular intervals, and users can manually save a version whenever they wish. Users can browse versions and revert the document’s contents to a chosen version using a Time Machine–like interface. The version browser is also used to resolve version conflicts from simultaneous iCloud updates.
Print documents. Users can specify various page layouts in the print dialog and page setup dialog.
Track changes and set the document’s edited status. The document manages its edited status and implements multilevel undo and redo.
Validate menu items. The document enables or disables menu items automatically, depending on its edited status and the applicability of the associated action methods.
Handle app and window delegation. Notifications are sent and delegate methods called at significant life cycle events, such as when the app terminates.
Cocoa’s document architecture implements most of its capabilities in three classes. These classes interoperate to provide an extensible app infrastructure that makes it easy for you to create document-based apps. Table 1-1 briefly describes these classes.
Creates, presents, and stores document data
Manages a window in which a document is displayed
Manages all of the document objects in the app
See The Classes That Support Document-Based Apps for more detailed information.
Storing Documents in iCloud
The iCloud storage technology enables you to share documents and other app data among multiple computers that run your document-based app. If you have a corresponding iOS version of your app, you can share your documents and app data with your iOS devices as well. Once your app sets up the proper connections, iCloud automatically pushes documents and changes to all the devices running an instance of your app with no explicit user intervention.
There are two kinds of storage in iCloud: document storage and key-value data storage. Document storage is designed for storing large amounts of data such as that in a document file. Key-value storage is designed for small amounts of app data such as configuration data. For example, you might store the text and illustrations for a book in document storage, and you might store the reader’s page location in key-value storage. That way, whenever the user opens the document on any device, the correct page is displayed.
Documents and key-value data designated for storage in iCloud are transferred to iCloud and to the user’s other computers as soon as possible. On iOS devices, only file metadata is transferred from iCloud to devices as soon as possible, while the file data itself is transferred on demand. Once data has been stored initially in iCloud, only changes are transferred thereafter, to make synchronization most efficient.
NSDocument implements file coordination, version management, and conflict resolution among documents, so it provides the easiest path to using iCloud. For details explaining how to handle document storage in iCloud, see Moving Document Data to and from iCloud.
The Document Architecture Supports App Sandbox
The document architecture helps document-based apps adopt App Sandbox, an access control technology that provides a last line of defense against stolen, corrupted, or deleted user data if malicious code exploits your app. The
NSDocument class automatically works with Powerbox to make items available to your app when the user opens and saves documents or uses drag and drop.
NSDocument also provides support for keeping documents within your sandbox if the user moves them using the Finder. For more information about App Sandbox, see App Sandbox Design Guide.
Considerations for Designing Your Document Data Model
Your document data model is an object or graph of interconnected objects that contain the data displayed and manipulated by your document objects.
Cocoa Uses the Model-View-Controller Design Pattern
The Cocoa document architecture and many other technologies throughout Cocoa utilize the Model-View-Controller (MVC) design pattern. Model objects encapsulate the data specific to an app and manipulate and process that data. View objects display data from the app’s model objects and enable the editing of that data by users. Controller objects act as intermediaries between the app’s view objects and model objects. By separating these behaviors into discrete objects, your app code tends to be more reusable, the object interfaces are better defined, and your app is easier to maintain and extend. Perhaps most importantly, MVC-compliant app objects fit seamlessly into the document architecture.
A Data Model Corresponds to a Document Type
A document object is a controller dedicated to managing the objects in the document’s data model. Each document object is a custom subclass of
NSDocument designed specifically to handle a particular type of data model. Document-based apps are able to handle one or more types of documents, each with its own type of data model and corresponding
NSDocument subclass. Apps use an information property list file, which is stored in the app’s bundle and named, by default,
<appName>-Info.plist, to specify information that can be used at runtime. Document-based apps use this property list to specify the document types the app can edit or view. For example, 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 app can edit or only view the type. For more information about creating a property list for types of documents, see Complete the Information Property List.
Data Model Storage
Any objects that are part of the persistent state of a document should be considered part of that document’s model. For example, the Sketch sample app has a subclass of
SKTDocument. Objects of this class have an array of
SKTGraphic objects containing the data that defines the shapes Sketch can draw, so they form the data model of the document. Besides the actual
SKTGraphic objects, however, the
SKTDocument object contains some additional data that should technically be considered part of the model, such as the order of the graphics within the document’s array, which determines the front-to-back ordering of the
Like any document-based app, Sketch is able to write the data from its data model to a file and vice versa. The reading and writing are the responsibility of the
SKTDocument object. Sketch implements the
NSDocument data-based writing method that flattens its data model objects into an
NSData object before writing it to a file. Conversely, it also implements the data-based
NSDocument reading method to reconstitute its data model in memory from an
NSData object it reads from one of its document files.
There are three ways you can implement data reading and writing capabilities in your document-based app:
Reading and writing native object types.
NSDocumenthas methods that read and write
NSFileWrapperobjects natively. You must override at least one writing method to convert data from the document model’s internal data structures into an
NSFileWrapperobject in preparation for writing to a file. Conversely, you must also override at least one reading method to convert data from an
NSFileWrapperobject into the document model’s internal data structures in preparation for displaying the data in a document window. See Creating the Subclass of NSDocument for more details about document reading and writing methods.
Using Core Data. If you have a large data set or require a managed object model, you may want to use
NSPersistentDocumentto create a document-based app that uses the Core Data framework. Core Data is a technology for object graph management and persistence. One of the persistent stores provided by Core Data is based on SQLite. Although Core Data is an advanced technology requiring an understanding of Cocoa fundamental design patterns and programming paradigms, it brings many benefits to a document-based app, such as:
Incremental reading and writing of document data
Data compatibility for apps with iOS and OS X versions
For more information, see Core Data Starting Point.
Custom object formats. If you need to read and write objects without using
NSFileWrapper, you can override other
NSDocumentmethods to do so, but your code needs to duplicate what
NSDocumentdoes for you. Naturally, this means your code will have greater complexity and a greater possibility of error.
Handling a Shared Data Model in OS X and iOS
Using iCloud, a document can be shared between document-based apps in OS X and iOS. However, there are differences between the platforms that you must take into consideration. For an app to edit the same document in iOS and OS X, the document-type information should be consistent. Other cross-platform considerations for document-data compatibility are:
Some technologies are available on one platform but not the other. For example, if you use rich text format (RTF) as a document format in OS X, it won’t work in iOS because its text system doesn’t have built-in support for rich text format (although you can implement that support in your iOS app).
The default coordinate system for each platform is different, which can affect how content is drawn. See “Default Coordinate Systems and Drawing in iOS” in Drawing and Printing Guide for iOS for a discussion of this topic.
If you archive a document’s model object graph, you may need to perform suitable conversions using
NSCodermethods when you encode and decode the model objects.
Some corresponding classes are incompatible across the platforms. That is, there are significant differences between the classes representing colors (
NSColor), images (
NSImage), and Bezier paths (
NSColorobjects, for example, are defined in terms of a color space (
NSColorSpace), but there is no color space class in UIKit.
These cross-platform issues affect the way you store document data in the file that is shared between OS X and iOS as an iCloud document. Both versions of your app must be able to reconstitute a usable in-memory data model that is appropriate to its platform, using the available technologies and classes, without losing any fidelity. And, of course, both versions must be able to convert their platform-specific data model structures into the shared file format.
One strategy you can use is to drop down to a lower-level framework that is shared by both platforms. For example, on the iOS side,
UIColor defines a
CIColor property holding a Core Image object representing the color; on the OS X side, your app can create an
NSColor object from the
CIColor object using the
colorWithCIColor: class method.