Fri 04 Feb 2005

Applescript vs. Cocoa

Ken Ferry mailed me about my iTunes controller, wondering what the overhead was for using Applescript in my Lisp controller.

With a little experimentation I found that calling out to the shell added about 350ms to the runtime for each call, plus execution time. To refresh a page which interrogates iTunes for the current track, the current volume, and whether it was set to play on random or not would take well over a second just to call the scripts.

I decided to investigate further, and I've managed to get a massive speed boost (indeed, I now use the Web interface rather than my shell scripts!). As an example, to increase the volume this function was used:
(defun volume-up ()
(run-program "vu" ()) t)
Now, we close over a precompiled Applescript object, and provide a function to run it:
(defun make-volume-up ()
(let ((vu-event (make-instance 'ns:ns-apple-script
:init-with-source #@"tell application \"iTunes\"\
to set sound volume to (sound volume + 10)")))
(send vu-event :compile-and-return-error
(defun volume-up ()
(send vu-event :execute-and-return-error
The same approach is taken for the other functions, including those that return a value — in fact, we can call methods on the response to extract string or boolean values, which is better than relying on the string response from the shell. Page refreshes are now almost instantaneous.

One thing that I had to pay attention to: autorelease pools. We're creating objects in the script responses themselves, and these need to be in an autorelease pool to stop warnings. To do this, I wrapped each handler's body, and the calls to make-volume-up &c, in with-autorelease-pool macro calls.

I'll post the code when I get the chance, but there you have it: it's a lot quicker to construct NSAppleScript objects and execute them than it is to escape to the shell and pass a string into osascript. As with most things, though, it is also more effort. Pick your poison.

Posted at 2005-02-04 04:04:08 by RichardLink to Applescript vs. Co…