Cocoa Design Patterns: Model-View-Controller
Model View Controller (MVC) is one of the oldest and most successfully reused software design patterns. It was first introduced with the Smalltalk programming language in the 1970s. MVC defines the overall architecture of the Cocoa frameworks. It’s a high-level pattern for organizing large groups of cooperating objects into distinct subsystems: the Model, the View, and the Controller.
To understand the roles that subsystems play in the MVC pattern, it’s useful to analyze the capabilities and behavior of common applications. Most applications store information, retrieve information, present information to a user, and enable a user to edit or otherwise manipulate the information. In an object-oriented application, information isn’t just bytes; objects encapsulate information along with methods for using the information. Each object within your application should fit into exactly one of the following subsystems:
- Model. The Model subsystem is composed of the objects that provide the unique capabilities and information storage for an application. Models contain all of the rules for processing application data. The Model is the key subsystem that makes an application valuable. It’s critically important that the Model subsystem is able to stand alone without dependencies on either the View or Controller subsystems.
- View. The View subsystem presents information gathered from the Model and provides a way for users to interact with information. The key to understanding Views is to recognize that there are invariably a multitude of Views. For example, there may be a graphical user interface View, a printed report View, a command line View, a Web-based View, and a scripting language View that all interact with the same Model.
- Controller. The purpose of the Controller is to decouple the Model from the Views. User interaction with a View results in requests made to the Controller subsystem, which in turn may request changes to information in the Model. The Controller also handles data translation and formatting for presentation to a user. For example, a Model may store data in meters, but based on a user’s preference, the Controller may convert the data to feet. A Model may store objects in an unordered collection, but the Controller may sort the objects before providing them to a View for presentation to a user.
The primary purpose of MVC is to decouple the Model subsystem from the Views so that each can change independently. The Controller subsystem enables that decoupling as shown in Figure 1.1. In a typical sequence of operations, the user interacts with a slider or some other interface object. The slider sends a message to tell a Controller object about the change to the slider’s value as indicated in step 1 in Figure 1.1. In step 2, the controller identifies which Model objects need to be updated based on the new value. The Controller sends messages to the Model objects to request the updates. In step 3, the Model objects react to the messages about updates. The Model objects might constrain the updated values to fall within application defined limits or perform other validation. Application logic is applied to the updated values, and other Model objects may be updated as a side effect. The Model then notifies the Controller that the Model has changed. Finally, in step 4, the Controller sends messages to View objects so that they reflect the changes that occurred in the Model. There may be many parts of the View that are updated.
Figure 1.1 The Controller subsystem decouples the Model and the View.
You might be tempted to neglect the Controller subsystem because it’s often tricky to design and seems like it adds needless complexity. After all, the flow of information is ultimately between the Model and the Views, so why introduce another layer? The answer is that Views tend to change much more often than Models. Not only are there potentially many Views, but it’s the nature of user interfaces that they change based on customer feedback and evolving user interface standards. It’s also sometimes important to change the Model without affecting all of the Views. The Controller subsystem provides insulation between the Model and the Views.
The dashed lines in Figure 1.1 emphasize the importance of using messaging approaches that minimize coupling. Ideally, neither the Model nor the View have dependencies on the Controller. For example, View objects often use the Target Action design pattern from Chapter 17, “Outlets, Targets, and Actions,” to avoid needing any information about the Controller objects that receive messages when the user interacts with user interface objects. Model objects often use the Notifications pattern in Chapter 14, “Notifications,” to broadcast notification of Model changes to anonymous interested objects that may be in the Controller subsystem.
MVC in Cocoa
Cocoa is loosely organized into Model, View, and Controller subsystems, as shown in Figure 1.2. Core Data simplifies the development of Models for many applications. The Application Kit contains objects for use in both the View and Controller subsystems. The Foundation framework provides classes used in all three subsystems. Foundation doesn’t directly provide any View or Controller-specific features, but it provides access to operating system services, the NSObject base class, scripting support, and other features used in the implementation of Models, Views, and Controllers.
Figure 1.2 The overall MVC organization of Cocoa
Apple supplies a diagram of the classes that comprise the Foundation framework at http://developer.apple.com/documentation/Cocoa/Reference/Foundation/ObjC_classic/Intro/IntroFoundation.html.
In addition to the overall MVC organization of Cocoa, important subsystems within Cocoa repeat the MVC design pattern on a smaller scale, for example, Cocoa’s Text Architecture groups collaborating classes into Model, View, and Controller roles within the narrow field of text processing. Cocoa’s Document Architecture is similarly divided into separate MVC components. Other Mac OS X technologies that aren’t strictly part of Cocoa reuse the MVC design as well. System preference panes, the Quick Time (QT) Kit, and Quartz Composer all separate their subcomponents into distinct MVC roles.
Core Data Support for Model Subsystems
Cocoa’s Core Data technology aids Model subsystem development and solves two common implementation challenges: persistent information storage and object relationship management.
Almost every Model needs the ability to store information and later reload it. Many possible implementations of load and store exist. Some applications use binary file formats, others use human readable text files, and some implementations rely on an underlying relational database. Core Data implements load and store with a technique called object persistence. The basic approach is to store the Model objects themselves including any encapsulated information and the relationships between the objects. Core Data can load and store the persistent objects from three file formats—human readable XML, binary flat files, or SQLite databases.
Whether Core Data is used, the design of persistent information storage needs to weigh several factors. Is the storage intended for easy information exchange between different applications? If so, a well-defined human readable format like XML is the best choice. How fast does the load and store need to be? Binary formats usually provide the best performance.
Core Data includes reusable infrastructure that abstracts the details of particular storage formats enabling you to concentrate on designing other aspects of your Model. You can change the preferred Core Data storage format for your Model at any time during development and even enable all three supported formats simultaneously.
Almost every Model needs to manage the relationships between objects. Core Data supports one-to-one and also one-to-many relationships between objects. Each relationship can be optional or required. If the members of a family are represented by objects, each member optionally has a relationship to one spouse but always has exactly two biological parents. Each member can have any number of children. Core Data enables you to specify relationships, identify constraints, provide default values, and validate relationships.
Although Core Data relationships can be specified through code, Apple’s Xcode tools include a graphical object modeler that lets you define your Model objects called entities and their relationships in a graphical way. The NSManagedObject class provides the built-in support for object relationship management and interacts with an NSManagedObjectContext to provide persistent storage. When designing your Model in Xcode, you can use NSManagedObject instances directly. NSManagedObject uses the Associative Storage pattern, which allows you to add relationships and per-instance information called attributes even without subclassing. Alternatively, you can create your own subclasses of NSManagedObject right in the modeling tool. Core Data natively stores attributes in NSNumber, NSData, NSString, and NSSet instances. Subclassing gives you maximum control over how attributes are stored so that you can use custom objects or C structures as attributes. Subclassing also provides a convenient way to add arbitrary application logic to Model entities.
Core Data is described in more detail in Chapter 30, “Core Data Models.” The support for relationship management, attribute changing, and persistent storage naturally integrates with Cocoa’s standard undo and redo features. Any change to an attribute or relationship can be undone. Core Data automates Attribute and relationship validation so that you can notify users about inconsistent or invalid changes to Model objects.
Application Kit Support for View Subsystems
Cocoa’s Application Kit contains classes used to build both View and Controller subsystems. Figure 1.3 identifies the most important View subsystem classes within the Application Kit. These classes present information and enable user interaction. Apple’s diagram of the entire Application Kit is available at http://developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/ObjC_classic/Intro/IntroAppKit.html.
The NSMenu, NSWindow, NSApplication, and NSView classes form the core of Cocoa graphical user interfaces. Almost everything displayed by a Cocoa application is part of a menu or a window. Each Cocoa application uses an instance of the NSApplication class to maintain a connection with the operating system for receiving user input events, displaying an icon in the dock, presenting a main menu, and displaying windows. Subclasses of NSView implement all of the standard user interface elements such as buttons, text, tab views, progress indicators, and image viewers that form the content of windows.
NSApplication, NSView, and NSWindow are all subclasses of NSResponder. The NSResponder class is one of the keys to the design of the Application Kit; it encapsulates handling of user input events and implements the Responder Chain design pattern to ensure events and messages are received by the right objects as described in Chapter 18, “Responder Chain.”
Figure 1.3 shows the many standard NSView subclasses that implement user interfaces. You can also create application-specific user interface features by making your own subclasses of NSView. Some of Apple’s other frameworks such as Web Kit, QTKit, and Quartz Composer provide specialized NSView subclasses for displaying and editing their respective media types.
Figure 1.3 Cocoa’s principal View subsystem classes
NSView implements the Hierarchies pattern (Chapter 16, “Hierarchies”) and enables you to compose interfaces that consist of views within views. Any view can contain any number of subviews. Interface Builder lets you easily develop your view hierarchy, and you can build it programmatically with NSView methods such as -(void)addSubview:(NSView *)aView, -(void)removeFromSuperview, and -(void) replaceSubview:(NSView *)oldView with:(NSView *)newView.
Some of Cocoa’s subclasses of NSView visually organize their subviews. For example, the NSBox class can draw a bezel around subviews to visually group them. The NSTabView class provides a visual metaphor for selecting and displaying any of several mutually exclusive subviews. NSSplitView uses a graphical divider bar to separate its sub-views either horizontally or vertically. Users drag the divider bar with the mouse to control how much of each subview is visible. NSScrollView repositions its subviews as users drag scroll bars.
Many of Cocoa’s standard user interface components are subclasses of the NSControl class. The NSControl class plays key roles in the Targets and Actions and the Responder Chain patterns explained in Chapter 17 and Chapter 18, respectively. For example, when a user selects a date via an NSDatePicker object, an action message is sent to the date picker’s target. If no specific target exists, the object that eventually receives the message is determined by the Responder Chain.
The NSCell class implements the Flyweight pattern and is explained in Chapter 22, “Flyweight.” Flyweights optimize both execution time and memory consumption. Instances of the NSControl class use NSCell subclasses as an optimization and to add flexibility. NSTableView is a prime example of the way controls use cells. Separate NSCell instances determine the way data is presented in each column. You don’t have to subclass NSTableView just to control how information is presented. Instead, configure the standard NSTableView with different embedded cells.
Application Kit Support for Controller Subsystems
Cocoa’s NSController class and related classes like NSArrayController fulfill the role of “mediator” between View objects and Model objects. Mediators control the flow of information and in some cases supply default “placeholder” values. For example, if a View object displays data based on the user’s current selection, a mediator can supply default data for use when nothing is selected.
The Application Kit supplies the NSController, NSObjectController, NSArrayController, NSUserDefaultsController, and NSTreeController classes that mediate data flow using Cocoa’s bindings technology. Bindings establish relationships between objects and are defined either programmatically or in Interface Builder. When a binding exists, changes made at runtime to each bound object result in automatic updates to the other bound object. Bindings can be made directly between Model objects and View objects or even between objects within a single subsystem, for example, between two View objects. However, bindings directly between View and Model objects produce all of the same problems as other dependencies between subsystems. Use NSController and its subclasses to mediate between the Model and the View. Bindings are explained in more detail by Chapter 32, “Bindings and Controllers.”
In addition to mediating the flow of data between the Model and the View, the Controller subsystem is also responsible for the overall control of application behavior. When multiple View subsystems are available, objects within the Controller subsystem are responsible for determining which Views to present to users. For example, when a script is run to extract data from a Model, there may be no need to display a graphical user interface View. The Controller subsystem is the ideal place to encode logic that determines whether to load and display a graphical user interface View. The Model can’t do it because the Model isn’t supposed to know what Views exist, and similarly different Views should not depend on each other.
The Application Kit contains several classes that control application behavior. Cocoa’s Document Architecture, described later in this chapter, highlights the NSDocumentController, NSViewController, and NSWindowController classes that control application documents, views, and windows, respectively.
Cocoa’s Text Architecture
The NSText and NSTextView classes shown in Figure 1.4 provide the user visible portion of Cocoa’s MVC text architecture. The NSTextStorage class provides a Model for storing and processing text. In cases in which the layout of text is key to application logic, NSTextContainer is also part of the text architecture’s Model. NSTextContainer stores the geometric shape of a block of text. For example, a drawing program that constrains text to a circular area may need to store that shape in the Model so that it’s restored if the Model is stored and later reloaded. However, in most applications, default rectangular text layout is sufficient, and no explicit NSTextContainer is needed.
Figure 1.4 The MVC components of Cocoa’s text architecture
The NSLayoutManager class acts as a Controller mediating between the View and Model. Each NSTextView instance asks an associated NSLayoutManager instance to provide text to be displayed. The NSLayoutManager in turn accesses instances of NSTextStorage and NSTextContainer to supply the text for display. In the process, NSLayoutManager converts Unicode characters into glyphs (graphical representations of characters) that are appropriate for display based on the current font, underline, and other attributes of the text.
In good MVC style, NSTextStorage is independent of text presentation. A wide range of text processing tasks are possible entirely within the Model subsystem. For example, text attributes can be changed, text itself can be modified, text can be searched, text can be served as web pages over a network, and text can be stored or loaded in a batch processing application that doesn’t display any user interface.
Cocoa’s text architecture provides a complete solution that meets the needs of most applications. It’s common to simply drag instances of NSTextView into your user interface via Apple’s Interface Builder application. Apple provides a tutorial to show you exactly how it’s done at http://developer.apple.com/documentation/Cocoa/Conceptual/TextArchitecture/Tasks/TextEditor.html. When you do want to customize text processing, the MVC design of the text architecture enables you to focus your effort on the appropriate subsystem. If you want to store nonstandard attributes along with text, use NSTextStorage or its superclass, NSMutableAttributedString, which is implemented in the Foundation framework. NSMutableAttributedString uses the Associative Storage pattern from Chapter 19, “Associative Storage,” so that you can most likely do what you want without subclassing. If you need to implement exotic text layout capabilities, start with NSLayoutManager, and if you want fine control over user input or you want to display custom text attributes, use subclass NSTextView.
Cocoa’s Document Architecture
Applications for viewing or editing information often adopt the user interface metaphor of “documents” presented in windows on-screen. Examples include spreadsheets, word processors, web browsers, and drawing programs. The Cocoa classes shown in Figure 1.5 implement a reusable MVC document architecture.
Figure 1.5 The MVC components of Cocoa’s document architecture
The document architecture adds an additional wrinkle to the MVC pattern. The Controller subsystem is divided into the Model Controller and the View Controller. The classes in the Model controller load, store, and access model data. The View Controller classes access already loaded Model data to enable presentation in the View.
The division exists in part to simplify the common practice of subclassing the NSDocument class. NSDocument is a prime example of the Template Method pattern described in Chapter 4, “Template Method.” NSDocument itself is abstract, so you must subclass it and implement a few critical methods to support loading and saving of Model data unique to your application. Another reason for the separation is that parts of the Controller layer are dynamically created as needed. For example, it’s possible to load and interact with a document without necessarily displaying any windows associated with the document. A script might open a document, copy information out of the document, and paste the information somewhere else without any need to display document windows.
The View Controllers, NSDocumentController and NSWindowController, mediate between the View subsystem and the Model to access information to be displayed or edited. There is only one instance of NSDocumentController in a document-based application. At any moment, there are separate instances of NSDocument corresponding to each open document. There may be zero, one, or more NSWindowController instances associated with each open document. The use of different numbers of instances of the various classes is another reason Cocoa distinguishes between document Model Controllers and View Controllers. Without the distinction, a single object would need to control all of the windows representing each document and document loading and saving.
In Mac OS X v10.5, Apple added the NSViewController class that fills a role similar to the NSWindowController class. NSViewController instances mediate between objects loaded from .nib files and objects that are outside the .nib file. Interface Builder makes it easy to establish Cocoa bindings that include NSViewController instances. NSWindowController and NSViewController simplify memory management for objects loaded from .nib files.
Figure 1.6 identifies the collaborations between the classes in Cocoa’s document architecture. Within a document-based application, one instance of NSDocumentController receives and processes messages to create new documents, load documents, and remind users to save documents before quitting the application. NSDocumentController also manages the contents of the standard Recent Documents menu. Graphical Cocoa applications contain an instance of the NSApplication class to enable use of menus and windows as described in the section, “Application Kit Support for View Subsystems.” The NSDocumentController instance receives delegate messages sent by the NSApplication instance. If you provide a different delegate object, your delegate will receive the messages that enable you to control document management behavior without having to subclass NSDocumentController. The ability to avoid subclassing in some cases is an advantage of the Delegates pattern explained in Chapter 15, “Delegates.”
Figure 1.6 Collaboration between the objects in Cocoa’s document architecture
You typically use one instance of the NSWindowController class to control each window associated with a document. A single document may be represented by multiple windows if necessary. For example, one window might present a document’s Model information as raw XML and HTML data while simultaneously another window presents the same Model information as a formatted web page. In another example, if a scheduled meeting is stored as a single document, one window might contain a table identifying all of the participants in the meeting, and other windows might display contact information for the people selected in the table.
Each NSDocument instance maintains an array of associated window controllers, and each window controller also knows which document it belongs to. This bidirectional communication lets the window controllers access the Model via the NSDocument instance. Similarly, when a document is hidden or closed, the NSDocument instance notifies associated window controllers.
Each instance of NSWindowController has an outlet that is typically connected in Interface Builder to the window to be controlled. You can create your own subclasses of NSWindowController and add additional Outlets and Actions. The Outlets and Actions are then connected to View subsystem objects like button and text fields to give the window controller direct access to those objects. An alternative but equally valid design uses Cocoa’s Bindings technology explained in Chapter 32 to configure user interface objects so that they always reflect the current information obtained from the Model via an NSDocument instance.
Your application’s Model contains the unique information used by the application. If you use Cocoa’s Core Data technology to implement the Model, an instance of NSManagedObjectContext provides access to the information and relationships within the Model and persistent storage in XML, binary, or database formats. Apple provides the NSPersistentDocument class, an already built subclass of NSDocument, to save and load using an associated NSManagedObjectContext. If you use the Core Data approach, you can frequently avoid the need to create your own subclass of NSDocument.
If you don’t use Core Data, you must create your own custom subclass of NSDocument and implement methods such as (BOOL)writeToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError and (BOOL)readFromURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError. Your implementations contain whatever logic and data conversions are needed to save and load the Model using a specific file type and file system location. The file type information enables document type conversions when saving and provides the information your code needs to convert from one type to another when loading. The error: parameter provides a way for your code to report any saving or loading errors that should be presented to users.
Whether you use Core Data, there is often a one-to-one correspondence between files on disk and documents. In other words, all of the information for one document is stored in one file. However, if you have more complex storage needs, Cocoa’s NSFileWrapper class encapsulates the details of using multiple files per document. Each NSFileWrapper instance manages a whole folder full of files at the location specified by the URL passed to -readFromURL:ofType:error:. NSFileWrapper can simplify saving and loading of complex Model information.
Cocoa’s MVC document architecture might seem complex at first, but in practice, you usually don’t have to spend much time directly interacting with the document architecture. It provides all of the standard behaviors that users expect and requires very little effort on the developer’s part. The key benefit of the MVC design is that you can tailor the behavior of multidocument applications without having to touch all of the components that collaborate to implement the full solution.
Apple’s Xcode application includes a Cocoa document-based application template that automatically creates an NSDocument subclass and other needed components such as the application’s NSDocumentController instance. The template even provides an Interface Builder file containing a window to use as the starting point for defining your document’s View subsystem. There is a similar Core Data document-based application template that automatically uses the NSPersistentDocument class.
A script interface is much like any other user interface. There are conventions that script writers expect just like there are standard look and feel expectations for graphical user interfaces. In the MVC design, a script interface is just one of the many View subsystems that an application may provide, and like all Views, scripting interfaces should usually interact with the Controller subsystem and not directly with the Model.
When providing a scripting interface, keep in mind that the optimal way in which script writers interact with an application often differs from the way a graphical user interface is used. Although it’s possible to write scripts that open windows and simulate button presses or menu selections, that’s seldom efficient. Ideally, a scripting interface works even if the application doesn’t display anything on screen. Scripts automate complex or tedious tasks and run in a batch mode.
Cocoa automatically provides basic support for scripting interfaces. The NSApplication object used in every graphical Cocoa application accepts interprocess messages and commands called “Apple events.” Apple events use a Mac OS X standard format for commands, arguments, and return values. The most common and popular scripting language used to send Apple events is Apple’s own Applescript. However, the cross-platform Python and Ruby scripting languages included with Mac OS X are also able to send Apple events. You can even generate Apple events from C and Objective-C programs including Cocoa applications. As of Mac OS X 10.5, Apple provides a technology called the Scripting Bridge that simplifies and standardizes the task of integrating scripting interfaces with Cocoa applications.
In addition to NSApplication, other Cocoa Controller layer classes support scripting. The NSDocument and NSDocumentController classes respond to standard AppleEvents related to document selection, loading, and saving. Cocoa’s text architecture handles the standard text manipulation Apple events for operations like insertion, deletion, text substitution, and searching.
You expose your application’s custom capabilities through new commands provided in a scripting “dictionary” that is stored as an application resource and loaded by the NSApplication class when needed. The scripting dictionary is usually an XML file that specifies how application-specific objects are selected or identified and what commands may be used with the objects. Script writers use the dictionary to determine what commands to send from scripts. Script development tools like Mac OS X’s Applescript Studio and Automator applications read the dictionary to validate scripts and detect scripting errors before the scripts are run.
Cocoa’s Preference Pane Architecture
Mac OS X’s built-in System Preferences application is extensible. It has a plug-in architecture with which new user interfaces called “panes” can be added. When started, the System Preferences application searches a set of standard file system locations to find and load any available plug-in panes. It then displays a window in which users choose among the available loaded preference panes. The System Preferences application as a whole uses the MVC design, and the panes that you add must also use that design.
The System Preferences Model consists of data files where systemwide and per user preferences are stored. Preferences include information like the chosen keyboard repeat speed, the desktop background picture, and the default method of connecting to the Internet. The System Preferences Model is encapsulated by the Core Foundation Preference Services interface, which is used by all applications and the operating system to access the preference values.
Each preference pane contains its own Controller subsystem created by subclassing Cocoa’s NSPreferencePane class. NSPreferencePane handles most of the work of interfacing with the System Preferences application. For example, whenever your preference pane is selected by a user, but before it is displayed, the -(void)willSelect message is sent to your Controller. Immediately after your View is displayed, System Preferences sends the -(void)didSelect message to your Controller. Whenever the user unselects your pane by selecting a different pane, closing the preferences window, or quitting System Preferences, the -(NSPreferencePaneUnselectReply)shouldUnselect message is sent to your Controller. Depending on the value returned from your implementation of -shouldUnselect, your pane can postpone the unselect action. As an example, your pane can implement -shouldUnselect to display an error message indicating any problems with the current preference values and warning that the values will not be saved.
Finally, each preference pane provides its own View subsystem so that users can interact with whatever preference values the pane is designed to access. Use Interface Builder to construct the View and connect it to the Controller with the Outlets, Targets, and Actions pattern described in Chapter 17 or bindings described in Chapter 29, “Controllers.”
As always in the MVC design, it’s critical that the actual preference values are stored in the Model and not just in the pane’s user interface. If a value is only stored in the pane’s user interface, then the value might as well not exist because the system and other applications will have no way to access it.
Quartz Composer’s Architecture
The Quartz Composer application is one of Apple’s free developer tools. It builds upon Mac OS X’s Quartz Core Imaging technology to create visual compositions using high performance graphical operations called “patches.” A group of interconnected patches along with source data such as images, colors, and text comprise the Model. The Model is essentially a recipe for creating visual compositions. The QCView is a subclass of NSView and is able to display final compositions. The QCPatchController class mediates between the Model and the QCView.
When you embed Quartz compositions in your Cocoa applications, you can connect your own user interface controls to an QCPatchController to influence the compositions displayed. For example, a group of patches may use a variable to specify how opaque the resulting composition should be. Your application can use actions or bindings so that the value of a slider sets the opacity variable via the QCPatchController instance.
The QTKit Architecture
Mac OS X’s QTKit is an Objective-C framework that manipulates and displays QuickTime media. The Model used by QTKit is the QTMovie class, which encapsulates movies, audio streams, animations, and other supported QuickTime media formats defined by the international MPEG-4 standard. QTMovieView is a subclass of Cocoa’s NSView class and displays QuickTime media to users.
To use the QTMovie and QTMovieView classes in a Cocoa application, you typically implement your own Controller layer. The Controller creates QTMovie instances and loads their content from files or over a network. The Controller then sends messages to tell a QTMovieView instance which QTMovie to play. Play, pause, fast forward, rewind, and other operations are implemented by sending messages to the QTMovieView.