objective-csmalltalkpharoobjective-c-runtimescripting-bridge

ScriptingBridge - How Does It Work "Behind the Scenes"


Context: I'm working on a Pharo/Smalltalk -> Objective-C bridge

Scenario: In the following Objective-C ScriptingBridge snippet:

iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];

iTunesTrack *currentTrack = iTunes.currentTrack; //[1]
// This low level way works too
//iTunesTrack *currentTrack = [iTunes propertyWithCode: 'pTrk']; //[2]

[iTunes playpause]; //[3]

Problem: The bridge uses class_getInstanceMethod to determine if an object understands a message/selector, but it returns NULL for scripting messages like playpause

Question #1 Why does class_getInstanceMethod return NULL for scripting messages like playpause? Same question for class_copyMethodList? What is special about scripting messages that they do not act like other Obj-C messages (except when they do!)?

Question #2 [SOLVED - see @Matt's answer]

Where, as per the docs, in the "dynamically defined subclass for the iTunes application" does SB put the "application-specific methods that handle the sending of Apple events automatically"? And, given that class_getInstanceMethod fails to find this behavior (see below), what's a reliable way for the bridge to test for it (i.e. whether such a method/message exists)?

The Objective-C Runtime API reports mixed results. On one hand, the iTunesApplication class seems not to have any methods (or properties for that matter):

On the other, #playpause can be queried and sent through other parts of the API:

Strangely, methodForSelector:@"playpause" successfully returns an IMP in Obj-C, but crashes if sent from the other side of the bridge.

Question #3 [SOLVED]

How one would simulate/replicate [3]?

Answered by @Willeke in comments: [iTunes sendEvent:'hook' id:'PlPs' parameters:0]


Solution

  • If SB doesn't use Objective-C messages, what do the docs mean by "subclasses of SBApplication implement application-specific methods that handle the sending of Apple events automatically"? Why does iTunes respondsToSelector: @"playpause" work i.e. return true? And how does [iTunes playpause] work? Etc, etc..

    It works because the very first thing you do in a scripting bridge application is generate a header. In Catalina you do it like this:

    sdp -f h --basename iTunes /System/Applications/Music.app/Contents/Resources/com.apple.Music.sdef
    

    This reads the iTunes dictionary (sdef) and generates the header for a group of analogous Objective-C classes. Now you have an iTunes.h file that you include in the application project and import in your code. It contains this line:

    - (void) playpause;  // toggle the playing/paused state of the current track
    

    So now playpause is declared, explicitly, as a legal command that you can send to an iTunesApplication object. Then, when you actually run your application, you say

    iTunesApplication* tunes = (iTunesApplication*)[SBApplication applicationWithBundleIdentifier:@"com.apple.music"];
    

    This causes your application to talk to iTunes (Music) and get the dictionary (sdef) again, generating the implementation for the methods declared in the header. The implementation for the playpause command is exactly what the sdef says it should be: namely, to send the hookPlPs event to iTunes.

    So that explains both why you are allowed to say playpause and what happens when you say it.

    That is what AppleScript is — it's an application supplying a list of things you can say to it using Apple events, along with English-like terms that reference those Apple events.

    So if you want to write a bridge, you have to do the same thing: you need to provide a way to scour the sdef resource of a target application and translate that information into a way for corresponding commands to be given in your language, whatever it is.