AppleScriptObjC の覚書で色々と気がついた所を書きましたが、それらは実際に触ってみないと分からないことだったりします。Hello World を表示するアプリケーションを作りながら、色々と確認してみます。
Xcode や Interface Builder の使い方は、ここでは取り扱いません。それらは既に優秀な解説があります。知識としては、Cocoa はじめの一歩から手に入る Become An Xcoder ぐらいの知識が必要です(後は練習問題 12 ぐらいまでができれば大丈夫です)。訳者様に感謝です。
HelloWorld アプリケーションの UI は以下のようになります。
では、多くを端折っていきます。Xcode で Cocoa-AppleScript Application の新規プロジェクトを作成したら、アプリケーションのコントローラーを追加します。「ファイル」メニューの「新規ファイル...」から AppleScript class を選択します。NSObject のサブクラスにしておきます。ファイル名は AppController としておきます。
AppController.applescript を開き、アウトレットとアクションを追加します。
script AppController
property parent : class "NSObject"
-- Outlets
property _window : missing value
property textField : missing value
property helloButton : missing value
-- Actions
on greeting_(sender)
textField's setStringValue_("Hello, World")
end greeting_
end script
アウトレットは property で宣言し、値は missing value にしておきます。アクションは引数を一つ取るハンドラとして定義します。引数を取るのでアンダースコアが必要です。Objective-C で書くなら次のようになります。
IBOutlet NSWindow *window;
IBOutlet NSTextField *textField;
IBOutlet NSButton *helloButton;
- (IBAction)greeting:(sender);
これらは通常ヘッダファイル(.h)の方に記述しますが、ASOC では、一つのスクリプトオブジェクトがヘッダファイル(.h)と実装ファイル(.m)を兼ねています。だから、上記のように書けてしまいます。あと、型の指定は当然できませんし、IBOutlet や IBAction などのキーワードも ASOC では利用できません。
なら、Interface Builder はどうやってそれらを識別するのかというと、識別されません。Interface Builder 上では全てが表示されます。ハンドラやプロパティが増えると、いささか鬱陶しいことになります。
ということで、MainMenu.xib を開いて Interface Builder で UI を作成し、アウトレットとアクションを接続します。
まず、MainMenu.xib のウィンドウに Library パレットから Object を追加します。
この Object を選択したまま、Inspector パレットで Class を AppController にします。
これで Interface Builder で AppController で定義したアウトレットとアクションが接続できるようになります。
このような感じになります。保存したら Interface Builder での作業は終わりです。Xcode に戻って「ビルドと実行」を行います。アプリケーションが起動したら、ボタンを押してみてテキストフィールドに「Hello, World」と表示されるのを確認してください。
以上の作業は、AppleScript Studio での作業とは全く異なります。というより、Cocoa/Objective-C でアプリケーションを作るのとほとんど同じです。その気になれば、バインディングも使えます。
では、このアプリケーションを叩き台に色々と調べてみます。まず、スクリプトオブジェクトの外にハンドラを定義します。
on greeting()
display dialog "Hello, World"
end greeting
greeting_ からこのハンドラを呼び出すように変更します。
on greeting_(sender)
-- textField's setStringValue_("Hello, World")
greeting()
end greeting_
実行してもエラーになると思います。これはどのようにしても無理なようで、スクリプトオブジェクト外にあるハンドラは呼び出すことができません。では、どうするかというとスクリプトオブジェクトにしてしまいます。
script HelloWorldDialog
on greeting()
display dialog "Hello, World"
end greeting
end script
このようにしておけば、通常のようにスクリプトオブジェクトのハンドラを利用できます。
on greeting_(sender)
-- textField's setStringValue_("Hello, World")
HelloWorldDialog's greeting()
end greeting_
つまり、これはクラスの定義ではありません。スクリプトオブジェクトの定義です。ゆえに、このスクリプトオブジェクト内ではメソッドを作成するように引数の数だけアンダースコアは必要ありません。
しかし、下手にこのスクリプトオブジェクトの親を指定してしまうと、クラスとして扱われるようになってしまいます。
script HelloWorldDialog
-- NSObject などを継承するとクラスになる
property parent : class "NSObject"
on greeting()
display dialog "Hello, World"
end greeting
on greeting_(yourName) -- 引数があるのでアンダースコアが必要になる
display dialog "Hello, " & yourName
end greeting_
end script
スクリプトオブジェクトをそのまま利用するか、クラスとして利用するか。どちらも一長一短です。クラスとして利用するなら、別のファイルにしていても property で指定するだけでそのクラスのメソッド(ハンドラ)が利用できます。が、メソッドの命名規則は Objective-C に準じます。
他方、スクリプトオブジェクトとして利用すると、Objective-C の命名規則から逃れられますが、別ファイルにしたときに読み込みが面倒になります。awakeFromNib などを利用して読み込みますが、path to me は利用できないので、NSBundle を利用します。
on awakeFromNib()
-- load script object.
tell NSBundle's mainBundle()
set theFile to pathForResource_ofType_("HelloWorldObject", "scpt")
end tell
set HelloWorldObject to (load script (theFile as text) as POSIX file)
end awakeFromNib
AppleScript の me は ASOC ではクラス自身を表すので、path to me は利用できません。また、上記では pathForResourceofType が返した NSString を AppleScript の text に型変換しています。AppleScript では NSString をそのまま利用できないようです。
Cocoa/Objective-C では色々と便利な関数が用意されています。それらは、ASOC では利用できません。例えば、矩形を表す NSRect を作成する NSMakeRect といった関数は使えません。これらはどのように作るのかというと、AppleScript の record クラスで代用します。
set rect to {origin:{x:10, y:10}, |size|:{|width|:100, height:100}}
このような record を作り、Objective-C のメソッドに渡せばいいようです。NSRange、NSSize、NSPoint なども同様です。NSLog は AppleScript の log 命令で代用できます。
アプリケーションを作成している時、特定のクラスを利用したい時があります。先ほどの NSBundle のように。Cocoa/Objective-C なら特に意識もしないで NSBundle のクラスメソッドを利用できたりするのですが、ASOC の場合、利用したいクラスを property で指定しておく必要があります。具体的には次のようになります。
property NSBundle : current application's class "NSBundle"
この property をどこで宣言しておくかというと、クラス(スクリプトオブジェクト)の外か、クラスの property として宣言します。
-- スクリプトのトップで宣言
property NSBundle : current application's class "NSBundle"
script AppController
property parent : class "NSObject"
-- クラスで宣言
-- property NSBundle : current application's class "NSBundle"
end AppController
これで NSBundle を利用できるようになります。Objective-C の import のようなものかと思うけど、そうではないようで、一気にクラスを参照したいがために AppKit.h や Cocoa.h を指定してもだめなようです。
property AppKit : class "AppKit" -- 利用できない
このようにクラスを property で参照しておくと、クラスが持っている定数等が利用できるようになります。
-- NSBundle's notification key
-- log NSBundle's NSBundleDidLoadNotification
log current application's NSBundleDidLoadNotification
その際、current application を通して定数を指定するようにします。コメントアウトしている方は利用できません。NSWindow の初期化処理を参考に。
script CustomWindow
property parent : class "NSWindow"
on initWithContentRect_styleMask_backing_defer_(contentRect, aStyle, bufType, frag)
continue initWithContentRect_styleMask_backing_defer_(contentRect, ¬
current application's NSTexturedBackgroundWindowMask, ¬
current application's NSBackingStoreBuffered, ¬
false)
return me
end initWithContentRect_styleMask_backing_defer_
end CustomWindow
横長になるのは AppleScript の宿命です。このように利用します。初期化処理ではスーパークラスの初期化処理を最初に呼び出しますが、ASOC では continue を使ってスーパークラスの初期化を行います。また、初期化の最後に Objective-C では self を返しますが、ASOC では me を返します。
Cocoa/Objective-C のメソッドの中には AppleScript の予約語になっているものがあります。例えば、size や width もそうですし、set や center といった語は AppleScript で定義されています。NSWindow にはウィンドウをディスプレイの真ん中に移動させる center というメソッドがありますが、これを利用するには | でメソッドを囲むようにします。
_window's |center|()
ちなみに上記は以下のようにも記述できます。
tell _window to |center|()
|center|() of _window
どの書き方でも構いません。Objective-C は以下のようにメソッドを連続して記述することがあります。
NSString *string = [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease];
ASOC で同じように記述するなら、s を使った以下の書き方が分かりやすいかもしれません。
set str to NSString's alloc()'s initWithData_encoding_(theData,current application's NSASCIIStringEncoding)'s autorelease()
横長なのは Objective-C でも AppleScript でも同じですね。今回は Objective-C の視点から ASOC を見てみました。ASOC のいいところは、AppleScript で Objective-C のような構文を記述しながら、既存の AppleScript を混在できるところにあります。
on awkeFromNib()
tell application "Finder"
set loc to insertion location as text
textField's setStringValue_(loc)
end tell
end awakeFromNib
Cocoa/Objective-C ほど面倒ではなく、AppleScript Studio ほどイライラせずにすみます。PyObjC のプロジェクトテンプレートが Xcode 3 からなくなったのには驚きましたが(古い Xcode から移植しましたら使えました)。ASOC がいつまでサポートしてくれるのでしょう。AppleScript Studio や PyObjC に比べると、サポートの手間は少ないと思うのですが。