Skip navigation.
 
mlRe: Problems with ScriptingBridge and iTunes
FROM : has
DATE : Mon Mar 03 00:02:37 2008

Jonathan 'Wolf' Rentzsch wrote:

> On Mar 1, 2008, at 7:28 PM, Hannes Petri wrote:

>> iTunesApplication *iTunes = [[SBApplication alloc] 
>> initWithBundleIdentifier:@"com.apple.iTunes"];
>> iTunesTrack *currentTrack = [iTunes currentTrack];
>>
>> if ([currentTrack isKindOfClass:[iTunesFileTrack class]]) {
>> …
>> }
>>
>> The problem is, that the class of the object returned is _always_ 
>> iTunesTrack, and not iTunesFileTrack, as i expect it to be. If i 
>> run the following applescript code:
>>
>> tell application "iTunes" to current track
>>
>> I get a "file track", which makes it possible to fetch the path 
>> using the "location" attribute. If I, in the ObjC example, try 
>> [currentTrack location], I'm told that it doesn't respond to that 
>> selector.
>> I have made certain that the object is of class iTunesTrack by 
>> typing 'po [currentTrack class]' in gdb.

>
> I ran into the same thing -- Scripting Bridge may play games 
> isKindOfClass: that bite us.


Short explanation: Scripting Bridge is made of LAME and FAIL.

Longer explanation:

The fundamental problem with Scripting Bridge is that it basically 
lies about how application scripting works.

The key concept to grasp is that Apple event IPC is based on RPC plus 
simple relational queries. It is *not* an object-oriented IPC 
mechanism à la DO/COM/CORBA/etc.

For convenience and conciseness, AppleScript implements a couple of 
magical behaviours at the compiler/interpreter level and dresses the 
whole lot up in an OO-like syntax. This OO resemblance is quite 
superficial, however, and beneath the surface the original RPC+query 
semantics are preserved intact. This makes AppleScript easier to use, 
at least for the common-case usages. The tradeoff is that, because the 
syntax obfuscates the semantics, it's difficult for users to form a 
complete, accurate understanding of how Apple event IPC actually 
works. Still, for the majority of AppleScript users, even a flawed, 
incomplete mental model is usually 'good enough' to get the results 
they need, so while these obfuscations do cause some problems they 
aren't disastrous.

Scripting Bridge, however, goes much further. Not only does it apply a 
generous amount of OO-like syntactic sugar on the underlying RPC+query 
mechanism, it also tries to impose its own set of OO-like semantics as 
well. This is a Bad Idea for various reasons, many of which previously 
I've discussed here and elsewhere (Google if you want to read more).


In this particular case, the first major problem is that because SB 
creates 'proxy classes' based on the class definitions in the 
application's dictionary, users naturally assume that instances of 
these classes directly represent objects in the scriptable 
application. This is, after all, how Distributed Objects and other 
object-oriented IPC mechanisms do things, after all: call a method on 
a proxy object, and the same method is invoked on the remote object 
and its result returned.

Alas, this assumption is completely and utterly wrong. Remember, Apple 
event IPC is RPC+queries, *not* OOP. While SB objects may look and 
superficially behave like DO-esque proxy objects, they are actually 
just thin wrappers around Apple event queries, dressed up to look like 
something else. Apple event queries have no real meaning in 
themselves; it's only when you stick them in an Apple event and send 
them to an application that they are evaluated to identify actual 
application objects. Therefore, by extension, there is no direct 
relationship between an SB object and an application object: calling a 
method on an SB object is not guaranteed to invoke an operation on a 
remote object; heck, an SB object isn't even guaranteed to map to a 
specific remote object, or the same object as the last time it was 
used, or any object at all!


Conversely, when you ask Scripting Bridge for [iTunes currentTrack], 
SB doesn't actually know what iTunes current track is, or what class 
it is, or even if it exists at all. To get that kind of information, 
you have to ask the application itself by sending it the relevant 
Apple event. All SB does is construct an Apple event query identifying 
the application object's 'current track' property and stuffs it in one 
of its faux-proxy objects for the client to manipulate further.

Which brings us to the second major problem: in order to present its 
pseudo-OO API, Scripting Bridge uses the type information given in the 
application dictionary and extracted from Apple event queries to 
determine which class of wrapper to put around a given query object. 
This wrapper then determines which operations you can perform using 
that query object - which would be perfectly okay if Apple event IPC 
followed OO rules, but it doesn't. As long as an Apple event query is 
grammatically correct it's perfectly legal to use, and it's up to the 
application evaluating it to decide what, if any, object(s) it 
identifies.

For example, 'document 1 of name of character "foo" of color of 
application "TextEdit"' is a completely legal query; it just won't 
locate a valid object when sent to TextEdit to evaluate, resulting in 
a runtime error. At first glance, this looks like an opportunity for 
Scripting Bridge to 'improve' on AppleScript by having the compiler 
prevent such nonsensical requests from ever being constructed. 
Unfortunately, the current laissez-faire approach taken by Apple event 
IPC - and, by extension, all of the applications that implement it - 
just weren't designed with this kind of usage in mind, and neither 
application dictionaries and implementations are guaranteed to be 
sufficiently detailed or accurate to reliably enforce such rigourous 
restrictions.

As a result, there's no guarantee that the class of object returned by 
[iTunes currentTrack] or any other property or element method bears 
any resemblance to the class of object that will actually be operated 
on when the query is evaluated. SB just creates a wrapper object of 
whatever class is specified by the 'current track' property in iTunes' 
dictionary. Since the dictionary says this property's type is track', 
the result is an instance of SB's 'iTunesTrack' class. Call its -class 
method, and you'll _always_ get 'iTunesTrack', regardless of what 
iTunes is playing.

Furthermore, if the class of object indicated by the 'current track' 
property in iTunes' dictionary doesn't define the properties or 
elements you want to use (in this case, 'location', then SB won't 
allow you to construct a query using those properties. Thus, even if 
you already *know* that iTunes is playing a file track, you still 
can't ask SB for [[iTunes currentTrack] location] because -location 
isn't a method on the 'iTunesTrack' object that SB previously 
returned. Your only option, short of resorting to cryptic four-char 
codes to build your query, is to send a 'get' event to iTunes asking 
it to locate the currently playing track ([[iTunes currentTrack] get]) 
and return a query to that, and hoping that the query it constructs 
contains sufficiently specific type information that SB will create a 
wrapper object of the class you need ('iTunesFileTrack') in order to 
find out the track's exact class and get its location if it's a file 
track. (Which, fortunately for SB, iTunes does; although there are 
some apps, e.g. Excel, that resolutely refuse to be specific.)


None of which is a problem in AppleScript, mind, since it has the good 
sense to treat Apple events more or less on their own terms even with 
the syntactic sugar:

   tell application "iTunes"
       if class of current track is file track then
           location of current track
       end if
   end tell

Ditto appscript, which learned these lessons years ago and is, if 
anything, even truer to Apple event semantics than AppleScript, e.g.:

   t = app('iTunes').current_track
   p t.location.get if t.class_.get == :file_track

Ironically, the principles behind Apple event IPC are really rather 
simple (if unusual), and the main reason so many folk struggle to make 
sense of it is that Apple have done an fantastically dreadful job of 
explaining it over the last decade-plus. Unfortunately, with Scripting 
Bridge, it looks as if instead of learning from and addressing those 
previous mistakes, Apple are determined to compound them with a whole 
new generation of really bad decisions. But it is their OS, and for 
those that wish a more reliable alternative to AppleScript there's 
always Python/Ruby/ObjC appscript, of course.

HTH

has
--
http://appscript.sourceforge.net

Related mailsAuthorDate
mlProblems with ScriptingBridge and iTunes Hannes Petri Mar 2, 02:28
mlRe: Problems with ScriptingBridge and iTunes Jonathan 'Wolf' Re… Mar 2, 06:06
mlRe: Problems with ScriptingBridge and iTunes has Mar 2, 13:05
mlRe: Problems with ScriptingBridge and iTunes Steven Degutis Mar 2, 13:54
mlRe: Problems with ScriptingBridge and iTunes has Mar 3, 00:02
mlRe: Problems with ScriptingBridge and iTunes Adam P Jenkins Mar 3, 00:16
mlRe: Problems with ScriptingBridge and iTunes Adam P Jenkins Mar 3, 00:40
mlRe: Problems with ScriptingBridge and iTunes has Mar 3, 01:44
mlRe: Problems with ScriptingBridge and iTunes Jens Alfke Mar 3, 07:32
mlRe: Problems with ScriptingBridge and iTunes has Mar 3, 16:21
mlRe: Problems with ScriptingBridge and iTunes Steven Degutis Mar 3, 16:27
mlRe: Problems with ScriptingBridge and iTunes Christopher Nebel Mar 3, 21:22
mlRe: Problems with ScriptingBridge and iTunes has Mar 3, 22:45