AppleScript_ The Definitive Guide - Matt Neuburg [250]
Separate code
As long as we're going to have separate accessors, we might want to separate the code that responds to scripting from the code that implements our application's internal functionality. A common architecture is to implement scriptability as an Objective-C category on the existing classes.
Add checks and error handling
At present, our application is very open to the user's commands; for example, a script can delete a person, give two persons the same name, or create a person with no name. We will want to close some of these doors and return a runtime error message to the script when the user tries to do something we disapprove of.
Implement objectSpecifier
Every AppleScript class that our application declares should have in its corresponding Objective-C class an implementation of the objectSpecifier method. This is what allows an object reference such as person "Matt" of application "Pairs" to be returned to a script when the user says something like get person 1 or make new person. Without an objectSpecifier implementation, the script will receive a meaningless reference.
Here's code that illustrates these points. First, I've changed two of the Cocoa keys in the dictionary: the person element of the application class now has Cocoa key "personsArray," and the name property of the person class now has Cocoa key "personName." These changes will allow our code to respond separately to the messages sent by the scriptability framework. I've moved all the scriptability code into its own file, where it is implemented through categories on the existing classes. I'll present it a piece at a time. First we have a general utility routine implemented as a category on NSObject, because every scriptable class will need it:
@implementation NSObject (MNscriptability)
- (void) returnError:(int)n string:(NSString*)s {
NSScriptCommand* c = [NSScriptCommand currentCommand];
[c setScriptErrorNumber:n];
if (s) [c setScriptErrorString:s];
}
@end
Observe how to return an error to AppleScript: you fetch the pending command and assign it an error number and, optionally, an error message to accompany it. (See Figure 3-1 in Chapter 3; the system holds out the incoming Apple event to your application like an envelope, from which you read the message and into which you insert any response, whether it's a result or an error.)
Next, we have the category on Person:
@implementation Person (MNscriptability)
- (NSScriptObjectSpecifier *)objectSpecifier {
NSScriptClassDescription* appDesc
= (NSScriptClassDescription*)[NSApp classDescription];
return [[[NSNameSpecifier alloc]
initWithContainerClassDescription:appDesc
containerSpecifier:nil
key:@"personsArray"
name:[self name]] autorelease];
}
- (NSString *)personName {return name;}
- (void)setPersonName:(NSString *)aName {
if ([[NSScriptCommand currentCommand] isKindOfClass: [NSCreateCommand class]])
[self setName:aName];
else
[master scripterWantsToChangeName:aName of:self];
}
@end
The implementation of objectSpecifier allows proper object references to be returned to AppleScript. We must specify the object's container, which in this case is the application class, and we must provide a key ("personsArray") matching the Cocoa key in the dictionary for how this element is accessed from that container.
Next we have the scriptability accessors for the name property, now keyed through "personName." A tricky architectural difficulty