Scripting

This section describes concepts that will help you design a scriptable Cocoa application. You should read this section before reading Cocoa Scripting Guide. However, if you are new to AppleScript and scripting, you should start by reading AppleScript for Mac OS X and Glossary of AppleScript Terms.

A scriptable application is one that can respond to Apple events. An Apple event is a type of high-level message used to send commands and data between processes. The AppleScript scripting system lets users automate tasks involving multiple applications by executing scripts, or series of English-like statements. Script statements are converted to Apple events and sent to the specified applications, which can include the Finder and other parts of the Mac OS.

To help you take advantage of the demand for scriptable applications, the Cocoa scripting architecture is designed to minimize the amount of work needed to make your application scriptable.

Scripting and the Model Layer

The scripting support in the Cocoa frameworks is geared towards making it easy for an application to implement scripting through its model objects. AppleScript has never encouraged scripting an application’s user interface because much of the time, the most efficient way for a script to do something is not the way a user might do the same thing in the application. If scripting were concerned solely with providing access to the user interface, it would just be glorified journaling.

Scripts that work with model objects are like batch processing. They perform their operations and don’t need or want the user’s involvement. A scripting system that extracts data from a database, processes it through other applications, then sends it all to a page-layout program to generate the classified ads page for a newspaper, is an example of such batch processing. The goal in batch-processing is not to involve the user, but to go directly to the application’s model objects to get the work done.

Sometimes, however, you may want to affect certain aspects of the user interface while scripting. Scripts that work with view objects are like macros. They do a very specific manipulation of an application, usually a relatively small and self-contained one, and their purpose is to automate a small repetitive task for the user. For instance, a script that gets the selected graphic in a page-layout program, places a caption beneath it, and sets up blue-line guides along the outer edges of the resulting group to aid with alignment, would be a script of this type.

Such a script is like a macro in that the user does a little preparation (such as selecting the graphic), invokes the script, then continues on when it is done. For this type of script, your application must make some of its user-interface structure scriptable—for example, you might need to make windows and selections scriptable. Making these user-interface structures scriptable should, however, be in addition to the support you provide for directly scripting model objects.

There are certain programming practices to avoid in designing your application’s model layer for scripting. Many simple applications keep state in their view layer (that is, in a user-interface object). For instance, a Preferences panel controller might be implemented so that the state of a Boolean attribute is “stored” in a checkbox in the Preferences panel and is retrieved and set with the state and setState: methods. However, keeping state in a view object is generally not a good strategy for data that is part of a document’s model because it is antithetical to the MVC pattern. If a script needs to be able to access and modify state, the state value should be separated from the view layer and stored in a model object or, if it doesn’t belong in the model layer, in a controller object. Often this separation is necessary or desirable even without scripting as a consideration.

For example, if a Preferences-panel controller stores current preference settings only in the controls of the panel, it cannot answer any questions about the current settings without loading the panel. If other parts of the application need to find out about preferences even if the user has not brought up the Preferences panel (a likely situation), then it would be much better if the preferences controller itself stored the settings. This would allow it to avoid having to load a nib file (a somewhat expensive activity) until it is actually needed.

The same argument holds for primitive behaviors as well. For instance, if you have a Find panel, instead of implementing the logic to actually perform the find in the action method invoked by the Find Next button, you should probably define some API in your document class or in your model objects that is capable of performing the find. The Find Next button’s action method would then invoke this API. The advantage of this scheme is that when you want scripts to be able to search documents, you can let the script go through the document or model API instead of requiring use of the Find panel itself.

Scripting and Key-Value Coding

Scripting in Mac OS X relies heavily on key-value coding to provide automatic support for executing AppleScript commands. In key-value coding, each model object defines a set of keys that it supports. A key represents a specific piece of data that the model object has. Some examples of scripting-related keys are “words,” “font,” “documents,” and “color.”

The key-value coding API provides a generic and automatic way to query an object for the values of its keys and to set new values for those keys. The primitive methods for key-value coding are valueForKey: and takeValue:forKey:. NSObject has generic implementations of these methods that first look to use standard accessor set and get methods based on the key (such as color and setColor: for the key named “color”). If the class of the object does not implement accessor methods, key-value coding directly sets or gets the value of the instance variable (“color”). Key-value coding defines many other extended methods that are implemented in terms of the two primitives, but these aren’t discussed here because they have little bearing on implementing scripting support.

As you design the objects of your application, you should define the set of keys for your model objects and implement the accessor methods. Then when you define the script suites for your application, you can specify the keys that each scriptable class supports. If you support key-value coding, you will get a great deal of scripting support “for free.”

Keys fall into three categories, which have their roots in relational databases. Keys are either attribute keys (for example, “color”), to-one relationship keys (a document’s NSTextStorage object), or to-many relationship keys (an application’s documents). This categorization makes sense in situations other than relational databases, including scripting. In AppleScript parlance, these key types map clearly to properties and elements. Think of AppleScript elements as relationship keys (where no distinction is made between to-one and to-many relationships) and think of AppleScript properties as attribute keys.

So why is key-value coding so important for scripting? In AppleScript, “object hierarchies” define the structure of the model objects in an application. For instance, a drawing application has documents and those documents have graphic objects. The graphic objects in turn have a fill color and line thickness. Most AppleScript commands specify one or more objects within your application by drilling down this object hierarchy from parent container to child element.

For instance, some graphics might be identified by the statement graphics 5 thru 7 of the document 'MyDocument' of application 'MyDraw'. There must be some way of finding these graphics so they can be acted upon. Key-value coding makes this search entirely automatic. An application has the key “documents,” which is a to-many relationship (because the application can open multiple documents). Each document has a “name” key that identifies the file it represents. To find the document named MyDocument the framework can ask for all the documents of the application and check each one’s name until it finds the one named MyDocument. Because key-value coding defines a uniform way of asking for the value of a key (valueForKey:), all this work can be done automatically with no extra effort from the developer. Similarly, once the key-value coding-driven scripting system finds the document, it obtains the “graphics” key and from it gets elements 5 thru 7.

If you have previous experience with AppleScript, you know that in a Carbon application, the work just described depends on the Object Support Library in the Mac OS. The Cocoa version of the Object Support Library knows how to use key-value coding to evaluate object specifiers. Instead of specifically invoking the library and passing in all sorts of evaluation handlers, the Cocoa developer simply relies on the key-value coding mechanism. Of course, you can be more directly involved in the evaluation if you need to do so for performance reasons or if your scripting model does not match your internal model closely enough for the automatic support to work.

The usefulness of key-value coding does not stop with object-specifier evaluation. Most of the core commands defined by AppleScript have default implementations in Cocoa based on key-value coding. For instance, the Get Data and Set Data commands require no extra code for your objects to support if the classes for these objects define their keys properly and implement the standard accessors. The same holds true of the Move, Clone, Delete, Create, Count, and Exists commands. Most script commands have been generically implemented with key-value coding so most model objects will not have to worry about them at all. If your model class must handle a particular command in a special way, even if the command has a default implementation, it can do so.

Object Specifiers

A script command is an AppleScript expression such as words whose color is red of the fourth paragraph of the front document of application 'TextEdit'. Within a Cocoa application, elements of this command are represented by objects of the NSScriptObjectSpecifier class, which use key-value coding to evaluate the underlying objects they represent. Concrete subclasses of this abstract class represent the different reference forms supported by AppleScript, such as index references (word 5) and filter references (tests or “whose” clauses, such as words whose color is red).

NSScriptObjectSpecifiers can be nested, so the example in the preceding paragraph would actually be represented by a chain of three references: one for the words, one for the paragraph, one for the document. (The phrase application 'TextEdit' does not need representation because the specifier exists in TextEdit by the time the command is executed.) NSScriptObjectSpecifier objects know how to evaluate themselves within their containing specifier. The explicit top-level specifier (front document in the example) evaluates itself within a default top-level container, which is usually the application itself.

You should not need to know much about specifiers to make an application scriptable, because Cocoa’s built-in scripting support can create and resolve specifiers automatically. However, you will need to know how to work with them if your application has scripting needs that go beyond the built-in support. For example, applications that wish to support recording will need to create object specifiers for recorded actions.

Script Commands

When a scripter executes a script that sends a command (such as set the height of the first rectangle to 37) to an application, the application receives an Apple event that encapsulates the script command. Cocoa’s built-in scripting support converts Apple events into script command objects based on the NSScriptCommand class. An application may receive many consecutive script commands, but each one is separate, distinct and complete.

A script command may be an instance of NSScriptCommand itself, but Cocoa also provides several subclasses of NSScriptCommand, whose default implementations use key-value coding to handle standard AppleScript commands such as Get Data, Set Data, and others. A subclass might also be needed if a command has arguments that need special processing to be converted to a useful form.

An NSScriptCommand object has an object specifier that identifies the receiver (or receivers) of the command and can have another object specifier for any arguments defined by the command. Command arguments can be actual values or object specifiers that identify where to find the actual values within the application’s object hierarchy.

A scriptable class declares what commands it supports. For commands that have a default implementation, scriptable classes can choose to use it, or they can choose to implement the behavior required by the command themselves. For commands without default implementations, scriptable objects must implement and specify a method that handles the command.

It may seem odd that script commands are separate from the classes that support them. Although this differs from standard object-oriented style, AppleScript is designed to have a small set of commands that act on a wide set of objects. This provides some advantages—for example, it gives the Cocoa frameworks the ability to support default implementations for commands, if the commands are generic enough to be implemented through key-value coding.

Script Suites

AppleScript groups chunks of scripting information in “suites.” A suite consists of a set of class descriptions, a set of command descriptions, and a set of terminologies for each supported AppleScript dialect. On Mac OS X, the suites an application supports are defined in the 'aete' resource of the application. In the Cocoa frameworks, suites are defined in property lists. You can create and examine property lists with the Property List Editor application, which is distributed with Mac OS X.

Any framework, application, or loadable bundle can declare script suites. The set of suites an application supports is a result of the union of all the suites defined by the application itself, the frameworks it links against, and the bundles it loads dynamically. The Cocoa frameworks declare two suites and thus any scriptable application automatically supports these suites. These suites are the Core suite and the Text suite. Thus, if you expose access to an NSTextStorage object through your object hierarchy, that NSTextStorage object is fully and automatically scriptable through the standard Text suite. If your application uses the Application Kit’s document architecture (discussed below), it automatically supports all the Core suite commands that can be applied to documents.

The property list that describes a suite contains all the information about the classes and commands in that suite that are needed by the scripting frameworks. For classes, this includes all the supported keys (attribute and relationship keys) for the class and their types. It also includes all the commands that the class supports (both from the class’s own suite and others). For commands this includes the number and types of the arguments, whether they are required, and the return data type. The suite definition also includes information needed to map the classes and commands to the appropriate four-letter codes used to structure the data in an Apple event representing a script command.

As a Cocoa application developer, you don’t typically have to deal with Apple events directly to support scripting, because Cocoa converts incoming Apple events into script commands. However, you will have to provide the information necessary to map classes, commands, keys, and related information to the codes used in Apple events.

In addition to the suite definition, which is a language-independent resource, a suite terminology contains dialect-specific terminology information that identifies the actual scripting vocabulary used for the various classes and commands.

Suite definitions and suite terminologies are described in more detail in Cocoa Scripting Guide.

Built-in Suites

The Cocoa frameworks define two standard suites, the Core suite and the Text suite. In addition, Cocoa classes implement scripting for these standard suites so that, for instance, the NSTextStorage object is completely scriptable using the Text suite and the NSDocumentController and NSDocument objects support the Core scripting commands that make sense for documents.

Custom Suites

Any application can define its own suites. In these suites they can define new script classes and new script commands.