AppleScripts may be executed within Cocoa processes via the NSAppleScript
class or (in 10.8+) in a subprocess via NSUserAppleScriptTask
. Both classes serve much the purpose, providing Cocoa apps with the basic ability to load and call/execute user-supplied AppleScripts. The main differences are that NSAppleScript
can persist a loaded script's state across multiple calls, while NSUserAppleScriptTask
lacks persistence support but executes scripts outside the main process's sandbox so doesn't require entitlements to interact with other apps.
Cocoa's NSAppleScript
class can be used to load existing AppleScript files or compile AppleScript code from scratch. The resulting script object is retained for the lifetime of the NSAppleScript
instance, allowing its handlers to be invoked any number of times and values stored in its properties to persist.
In basic usage, -executeAndReturnError:
may be used to invoke the script's implicit/explicit run
handler without parameters. The return value is an NSAppleEventDescriptor
containing the script's return value, if any, or nil
if an error occurred. Additional error information may be obtained via the optional error argument.
In order to pass parameters or invoke other handlers, the -executeAppleEvent:error:
method should be used. (While it is possible to dynamically generate AppleScript code and execute it, that approach is subject to all the usual dangers of arbitrary code evaluation, including poor performance and security holes.) The handler name may be a dictionary-defined keyword (e.g. open
) or a user-defined identifier (e.g. doSomething
). Where keywords are used, the Apple event should be constructed using the appropriate four-character codes (OSTypes) for the event class and id, and for any positional arguments. Where user-defined identifiers are used, standard event class and id codes are used, and the handler name and any arguments are passed as standard parameters.
All input values must be packed as NSAppleEventDescriptors
. All return values must be unpacked. This is somewhat tedious, but could easily be abstracted away into a separate library or class for repeated use.
As of 10.6, user-defined handler names are case-sensitive. (In 10.5 and earlier, the name was always treated as all-lowercase except when enclosed in pipes.)
Python and Ruby scripts can use NSAppleScript
via the PyObjC
and RubyCocoa
modules respectively. MacRuby can access NSAppleScript
directly.
The following Objective-C code compiles a simple AppleScript from source, then calls its addTo
handler (where addTo
is a user-defined identifier):
#import
#import
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSAppleEventDescriptor *params, *event, *resultDesc;
// load AppleScript code
// (tip: to read directly from .scpt file, use -[NSAppleScript initWithContentsOfURL:error:])
NSAppleScript *script = [[NSAppleScript alloc] initWithSource: @" on addTo(a, b) \n"
@" return a + b \n"
@" end addTo "];
// pack positional parameters
params = [NSAppleEventDescriptor listDescriptor];
[params insertDescriptor: [NSAppleEventDescriptor descriptorWithInt32: 2] atIndex: 1];
[params insertDescriptor: [NSAppleEventDescriptor descriptorWithInt32: 3] atIndex: 2];
// build Apple event to invoke user-defined handler in script
event = [NSAppleEventDescriptor appleEventWithEventClass: kASAppleScriptSuite
eventID: kASSubroutineEvent
targetDescriptor: NSAppleEventDescriptor.nullDescriptor
returnID: kAutoGenerateReturnID
transactionID: kAnyTransactionID];
[event setDescriptor: params forKeyword: keyDirectObject];
[event setDescriptor: [NSAppleEventDescriptor descriptorWithString: @"addTo"]
forKeyword: keyASSubroutineName];
// invoke handler
NSDictionary *errorInfo;
resultDesc = [script executeAppleEvent: event error: &errorInfo];
if (!resultDesc) {
NSLog(@"ERROR: %@", errorInfo);
} else {
NSLog(@"RESULT: %i", resultDesc.int32Value); // unpack return value
}
}
return 0;
}
Here is the same example implemented in Python, using the PyObjC bridge:
#!/usr/bin/python
import struct
from Cocoa import *
def fourcharcode(s): # convert a four-char code string to an unsigned int
return struct.unpack('>I', s)[0]
# load AppleScript code
scpt = NSAppleScript.alloc().initWithSource_(''' on addTo(a, b)
return a + b
end addTo ''')
# pack positional parameters
params = NSAppleEventDescriptor.listDescriptor()
params.insertDescriptor_atIndex_(NSAppleEventDescriptor.descriptorWithInt32_(2), 1)
params.insertDescriptor_atIndex_(NSAppleEventDescriptor.descriptorWithInt32_(3), 2)
# build Apple event to invoke user-defined handler in script
event = NSAppleEventDescriptor.appleEventWithEventClass_eventID_targetDescriptor_returnID_transactionID_(
fourcharcode('ascr'), fourcharcode('psbr'), NSAppleEventDescriptor.nullDescriptor(), 0, 0)
event.setDescriptor_forKeyword_(params, fourcharcode('----'))
event.setDescriptor_forKeyword_(NSAppleEventDescriptor.descriptorWithString_('addTo'), fourcharcode('snam'))
# invoke handler
errordesc, errorinfo = scpt.executeAppleEvent_error_(event, None)
if out is None:
print 'ERROR:', errorinfo
else:
print errordesc.int32Value() # unpack return value