AppleScript_ The Definitive Guide - Matt Neuburg [253]
Now let's implement a verb. Let's call this pair, and we'll have it apply to two person objects. One will be the direct object; the other will appear after a parameter, to:
pair person 1 to person 2
Verbs (commands) are implemented in two different ways in Cocoa scripting. If a verb basically applies to a single object, it can appear in the Objective-C code as a method in the class that corresponds to the class of that object. This is called object-first dispatch. (The other way of implementing a command, verb-first dispatch, is demonstrated later in this chapter.) Having defined the command in the dictionary, you then specify in the dictionary every class that can serve as the direct object to this command. So the dictionary will contain this definition of the command:
And in our person class, the dictionary now contains the following:
What this means is that when the user invokes the pair command, a message scripterSaysPair: will be sent to the Person object who represents the direct object of the command. The parameter to this method is an NSScriptCommand object whose evaluatedArguments method yields an NSDictionary containing the command's additional parameters, accessible through their Cocoa keys; in this case, there is just one additional parameter, and its key will be "otherPerson." So now we can implement scripterSaysPair: in our category on the Person class:
- (void)scripterSaysPair:(NSScriptCommand*)command {
Person* p1 = [command evaluatedReceivers];
Person* p2 = [[command evaluatedArguments] valueForKey:@"otherPerson"];
if (self != p1 || self == p2) {
[self returnError:errOSACantAssign string:@"Invalid pairing."];
return;
}
[master scripterWantsToPair:p1 with:p2];
}
After an error check, the command is passed on to MyObject for processing. The routine in MyObject (not shown here) does some more error-checking (making sure neither of the person objects is already paired) and then does whatever it usually does to pair two Persons. I use this architecture in order to distribute responsibilities appropriately; a Person can look to see whether the pair command makes basic sense, but it is MyObject, as master of the persons collection, who decides whether two Persons can be paired and, if so, pairs them.
It would be nice to complete the picture by adding a read-only boolean paired property to the person class, stating whether the person has been paired, along with a read-only partner property that returns a reference to the other person in the pair. These correspond to no instance variable of the Person class, which just goes to show that a sensible scripting interface needn't be like the underlying implementation.
- (id) personPartner {
Pair* myPair = [self pair];
if (!myPair) return [NSNull null];
return ([myPair person1] == self ? [myPair person2] : [myPair person1]);
}
- (void) setPersonPartner:(id)newPartner {
[self returnError:errOSACantAssign string:@"Partner property is read-only."];
}
- (BOOL) personPaired {
return ([self pair] != nil);
}
- (void) setPersonPaired:(BOOL)newPaired {
[self returnError:errOSACantAssign string:@"Paired property is read-only."];
}
The personPartner method returns an NSNull instance if the person hasn't been paired; that's how you cause missing value to be returned to AppleScript. Finally, observe that you must implement setter accessors even though the dictionary marks these properties as read-only.
Warning
When the user requests an object that doesn't exist (a person whose index number is too large, for example), an ugly and obnoxious "NSReceiverScriptError " runtime error message is returned by the scriptability framework. Various workarounds have been proposed, and I have