Scripting Bridge code is very much like any other Objective-C code. However, there are some differences, mostly deriving from the OSA architecture on which the dispatch and handling of Apple events is based. This chapter describes how to use Scripting Bridge in Objective-C projects, pointing out those differences along the way. Improving the Performance of Scripting Bridge Code also discusses the correct or optimal use of Scripting Bridge, but from a performance angle.
Generate header files for all scriptable applications that your code is sending messages to.
Add these files to your project.
In your header or implementation files, add #import statements for the generated header files.
Add the Scripting Bridge framework to your project.
You can use id to type Scripting Bridge objects dynamically, but such code is vulnerable to programming errors and results in lots of warning messages when you build your project. It is recommended that you generate a header file for each scriptable application, add these files to your project, and in your code give Scripting Bridge specific types based on what you find in the header files. Doing so allows the compiler to check the types of objects (eliminating spurious warning messages) and gives you more assurance that you are sending messages to the correct recipients.
Before you can send messages to a scriptable application, you need an object that represents the application. As explained in Classes of the Scripting Bridge Framework, the Scripting Bridge framework declares three class factory methods for creating instances of scriptable applications; each takes a different value for locating the application.
applicationWithBundleIdentifier: locates an application by its bundle identifier.
applicationWithURL: locates a (local or remote) application by URL.
applicationWithProcessIdentifier: locates an application by its BSD process identifier (pid).
If you don’t know an application’s bundle identifier, you can find it by looking for the value of CFBundleIdentifier property in the Info.plist file stored in the application bundle.
There might be occasions when using one of the other class factory methods makes sense. For example, if you are writing an application that uses Scripting Bridge for your own personal use, you could refer to applications by URL. The following example creates an instance of the Pages application, locating it by URL in an installation location other than /Applications.
To control a scriptable application, simply send to the instance of the application or one of its objects a message based on a method declared by the object’s class. These methods correspond to commands in the application’s scripting definition. The action method listed in Listing 2-1 plays the currently selected iTunes track and then modulates the volume of the sound, eventually restoring it to the original level.
Note that this method tests whether the application is running before it attempts to control it. (The isRunning method is declared by the SBApplication class.) This programming practice is discussed in more detail in Improving the Performance of Scripting Bridge Code.
In the object graph that Scripting Bridge dynamically generates for a scriptable application, most objects are containers of other objects, or of objects that refer to another scripting object. In a data-modeling sense, they express to-many or to-one relationships and enable your code to “drill down” the object graph. It is only when you get to the leaf nodes of the graph, which are typically the properties of an object, that you are able to access concrete data, such as a name, a color, or a numerical value. As you may recall, Scripting Bridge implements properties of scripting objects as declared properties in Objective-C.
Getting the value of a property requires you to navigate the object hierarchy of the application until you come to the target object—that is, the object that declares those properties—and then send a message to that object that is the same as the property name. Sometimes you don’t have to navigate that far. For example, the fragment of code in Listing 2-2 sends two messages to the ITunesApplication object.
The first message to the application gets the value of its currentTrack property; this message yields an object of class iTunesTrack representing the track currently playing. This object does itself not represent any concrete data, but the message then sent to it (name) returns the value of its name property as an NSString object.
Important: If you call an accessor method to get a property value of a type descended from SBObject (for example, an iTunesTrack object), you receive an SBObject object even if there is no “real” object set. In a sense, this is an “empty” object that refers to nothing in the target application. Attempting to evaluate the reference results in an error. In such cases, it is recommended that you test the returned object with the exists method to determine if it is a reference to a real object.
You can also set the values of properties unless they are (in their declaration) marked as readonly. The code in Listing 2-3 implements a command-line tool that clears the locked property in items in the Trash.
However, you could rewrite the Scripting Bridge code in Listing 2-3 to be more efficient by using the setValue:forKey: method of NSArray. The example in Listing 2-4 sends just one Apple event instead of one event per item in the Trash.
Note: See Using Element Arrays for more information on other array-processing approaches and Improving the Performance of Scripting Bridge Code for a discussion of related performance techniques.
Sometimes asking a scripting object for the value of a property might not return the expected concrete data. This happens if the returned value itself is an instance of an application-specific subclass of SBObject, and thus an object specifier referring to what may be the concrete data. Consider the example in Listing 2-5. This code copies the textual content of each selected Mail message to a TextEdit document.
The last line of code in the example includes the message [message content], but this message does not yield the textual content of the message as one might expect. Instead, a get message is required to force evaluation and thereby obtain the text for assignment. What [message content] returns in an instance of MailText, which a subclass of SBObject. This instance is an object specifier that refers to the actual text. For more on forced evaluation of object specifiers through the get method, see Improving the Performance of Scripting Bridge Code.
Element arrays, or SBElementArray objects, are collections of scripting objects of the same type. Scripting Bridge implements the elements it finds in an application’s scripting definition as methods that return SBElementArray instances. Because SBElementArray is a subclass of NSMutableArray, you can send methods to element arrays that are declared by NSMutableArray and its superclass, NSArray. For example, as shown in Listing 2-6, you could use an NSEnumerator object to enumerate through the array.
Or you could even use the fast enumeration feature of Objective-C 2.0 to more efficiently iterate through the array, as shown in Listing 2-7.
Use makeObjectsPerformSelector:withObject: (NSArray) to make contained objects perform an operation.
Use valueForKey: and setValue:forKey: (NSArray) for bulk fetching and setting, respectively, of the properties of objects in an element array.
Use filteredArrayUsingPredicate: (NSArray) to obtain a subset of the objects in the array.
Use arrayByApplyingSelector:withObject: (SBElementArray) to get a particular value from each object in the array.
The code in Listing 2-8 uses the last of these methods to create a pop-up list with the calendar names obtained from the iCal application.
The code fragment in Listing 2-9 (which is extracted from the method shown in Listing 2-11) uses the filteredArrayUsingPredicate: method to create an array containing a subset of calendar events that each have a particular name.
// handle any events that match..
Important: If you call an element accessor method (for example, objectWithName:) to get an object from an element array, you receive an SBObject object even if there is no “real” object with that name, ID, location, or index in the array. In a sense, this is an “empty” object that refers to nothing in the target application. Attempting to evaluate the reference results in an error. In such cases, it is recommended that you test the returned object with the exists method to determine if it is a reference to a real object.
For adding scripting objects to element arrays, you may use NSMutableArray methods such as insertObject:atIndex: and addObject:. To remove objects from element arrays, call removeObject: (or a related method of NSMutableArray) on the element array.
Call classForScriptingClass: on the application instance to get the Class object to use for allocating the object.
This method takes the name of the class as specified in the scripting definition—for example, document. Do not use a class name in the sdp-generated header file as the receiver of the alloc method.
Immediately after creating the object, insert it in the appropriate element array.
The object is not “viable” in the application until it has been added to its container. Consequently, you cannot set or access its properties until it’s been added.
Listing 2-11 provides an extended example of conditionally creating and inserting iCal calendar and event objects, showing both approaches.