AppleScript Studio から Cocoa API を利用する

久々に AppleScript Studio の話題を。先日、applescript-studio ML に、

  • call method コマンドで Cocoa API を呼び出せるのはいいが、膨大な API の中には、AppleScript がネイティブに対応しているものもある。本当に必要な API がどれかを判断するには、どうしたらいいのか

  • また、その必要な API を AppleScript でラップして、AppleScript の知識だけあれば使えるような、ライブラリのようなものを作ったらどうか

という趣旨(たぶん)の 投稿 がありました。

で、僕はそれに返答を試みたのですが、まず英語に関して、読解力は、なんとかキープできていると思いますが、記述力は、もともと無いうえに、さらに悪化、高校レベルの基本的な文法を忘れている気がします。来年の目標が一つ確定(涙) 結局、自分が思ったことを、自分の記述力で漉してみたら、ちょっとしか汁が出なかった(?)という状態だったので、(日本語なら大丈夫かは別として)ここでリベンジしてみようかと。

どうやって必要な API を見分けるか

まず、最初の「どうやって必要な API を見分ければいいのか」ということですが、これは、見分ける方法を探そうとするよりも、素直に Cocoa/Objective-C を勉強して、それと AppleScript Studio がどのように関連しているのか、学ぶべきだと思います。

「特殊な型の引数をとるメソッドは呼び出せない」とか「通知を受け取れない」とか「それをするにはどうしてもサブクラス化が必要」とか、ひとつひとつ挙げていくこともできるかもしれませんが、前述のような知識があれば、自然に分かることだと思います。

例えば、Cocoa で NSTableView を使って表を表示させる方法が分かっていれば、それが AppleScript Studio 上のクラスやイベントハンドラとどう対応していて、call method コマンドを使えば、どういった追加の利益が得られるかは、だいたい分かると思います。

そもそも、そのアプリは AppleScript Studio ベースでないといけないのでしょうか。Leopard で搭載されたスクリプティングブリッジを始めとして、いまは多くの選択肢がありますから、僕のように趣味でやっているのでない限り、その辺はシビアに考えるべきかもしれません。もし AppleScript Studio ベースで、しかもその枠を超えたことをしたいなら、普通に Cocoa/Objective-C アプリを作るくらいの覚悟がいるかもしれません。

ライブラリは必要か

また「必要な API をラップして、ライブラリ化したらいいんじゃないか」ということですが、まず、いま Apple が、Cocoa のパワーを Studio にも取り入れようと、必死で頑張ってくれているはずです!(かなり疑わしい 笑)僕たちがそれをすると、作業が重複してしまいます。また、ライブラリのようなものが出来上がったとして、今のところ AppleScript には、そういったものをうまく使いこなす機能が備わっていません( #import みたいのが欲しいな)。むしろ、前述のような知識があれば、必要な部分だけをピンポイントでラップして、効率の良いスクリプトにできるかもしれません。

サンプルプロジェクト

ところで、「ラップする」と言えば、僕も今まさに Kaku などで、AppleScript Studio がサポートしていないオブジェクトの「ラッパー」らしきものを作って、利用しています。そこで、そうしたラッパーに関連したコードを含んだ、僕が Kaku などで使っている基礎的なコードを集めて、サンプルプロジェクトを作ってみました。

ダウンロード

CallMethodOrientedProject.zip (63KB)

ライセンス

修正 BSD

また、call method コマンドは、少しなら問題ありませんが、使う箇所が多くなってくると、かなりスクリプトが汚くなってくる、という気が(個人的には)します。このサンプルプロジェクトは、そういった問題への対処の一例にもなっていると思います。もし同じような問題で困っている方がいらしたら、見てみてください(たぶん、いない)。

サンプルプロジェクト — 解説

call method 実行のための関数群

このプロジェクトに含まれているコードの特徴としては、まず、call method を使うときは、直接 call method 文を書くのではなく、スクリプトのトップレベルにある 、

  • cmoo() — 引数なしのインスタンスメソッドを実行
  • cmoc() — 引数なしのクラスメソッドを実行

といった関数群を使います。これには、タイプするのがめんどい、という理由のほかに、メソッドの返り値で nil が返ってきたとき対処するため、という理由もあります。例えば、

set var to call method "objectForKey:" of dictionary with parameter "unknownKey"

といった文で nil が返ってきたとすると、AppleScript には nil かどうか調べる手段がない、というか、var の中身を取り出そうとした瞬間にエラーが発生するので、厄介です。前述の関数では、これらを事前にチェックして、もし nil なら、代わりに missing value を返します。

以下は、ちょっと混乱しそうですが、test という文字列を保持している NSString のインスタンス( stringNSString は相互に変換される)の、uppercaseString というメソッドを呼び出す例です:

cmoo("uppercaseString", "test") -- "TEST"

ラッパーオブジェクト

ここからが「ラッパー」の話になりますが、プロジェクトを実際にご覧いただいた方は、スクリプトの多くの部分が、スクリプトオブジェクトの組み合わせで書かれていることにお気づきかと思います。ラッパーは、script Object を継承する script Wrapper として定義されています。次はこの、Wrapper オブジェクトを使ってみます:

set wrappedString to wrapperWithObject("test") of SO_Wrapper() -- Wrapper の生成
cm("uppercaseString") of wrappedString -- "TEST"

ちょっと、AppleScript 標準のオブジェクトを扱うようなスタイルに近づいてきました。

カスタムラッパーオブジェクト

さらに Wrapper を継承して String というオブジェクトを定義してみます:

on SO_String()

    script |String|

        property parent: SO_Wrapper()

        on stringWithString(givenString) -- 初期化メソッド
            continue wrapperWithObject(givenString)
        end stringWithString

        on uppercaseString()
            return cm("uppercaseString")
        end uppercaseString

    end script

end SO_String()

----

set theString to stringWithString("test") of SO_String()
uppercaseString() of theString -- "TEST"

最後の実行文は、完璧とは言えないものの、だいぶしっくりくるようになったのではないでしょうか。他の種類のオブジェクトのためのラッパーオブジェクトも、上図のようにシンプルに Wrapper を拡張して、必要な機能だけを持ったものを、簡単に作れます。僕はこれを使って、Kaku などで、NSArrayController や WebView をコントロールしています。

サンプルプロジェクトは、NSArrayController で管理されている URL リストに URL を追加し、その URL を WebView で表示する、というデモになっていて、NSArrayController の参照を取得する部分以外は、すべて AppleScript で動かしています。よろしければ、試してみてください。

“AppleScript Studio から Cocoa API を利用する” への2件の返信

  1. 僕も、同意見で AppleScript と Cocoa の間のレイヤーを厚くするのは得策じゃないんじゃないかと思います。

    Objective-Cは難しくないし、GUI のプログラムするなら、AppleScript より Objective-C で書いた方がらくちんですからね。

    それより、Objective-C から AppleScript Studio でGUI に bind されているスクリプトのインスタンスへアクセス方法を用意してくれるとか、もっと他にもいろいろすべきことがあるように思えます。

  2. applescript-studio ML でもこれに関連した議論がヒートアップしていました。読むのにすごい時間がかかりそうになってきたので、ついていくのを諦めましたが(汗)

    やはり、AppleScript に限らず、どんな環境でも、自分がそれを改善できる立場にないのであれば、現状でなんとかするか、他の環境を試してみるのが、いちばん手っ取り早い気がします。

    > Objective-C から AppleScript Studio でGUI に bind されているスクリプトのインスタンスへアクセス方法を用意してくれるとか、

    以前、くりたさんのサイトで、その方法を教えていただきました。せめてこの辺さえ用意されていれば、Apple の開発者がそんなに頑張らなくても、ユーザー側でできることが増えそうなのにな、と思います。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です