Cocoa Break Logo Top Soft Develop  脱力空間 Logo
Apple Web Badge
made by mi

概要 翻訳 ソース リンク がらくた

概要 Examples ADC Samples 3rd Parties etc CBOriginals

SimpleService

以下は、Mac OS X v10.4.10 上の Xcode 2.5 で説明しています。

目次:
1 README.rtf の日本語訳
2 アプリケーション
3 ファイル構成
4 Info-SimpleService.plist
5 SimpleService_main.m
6 ServiceTest クラス
6.1 クラス宣言
6.2 「Open File」サービス
6.3 「Capitalize String」サービス
7 まとめ

1 README.rtf の日本語訳

SimpleService は、サービスメニューのためにサービスを作成することを示す小さなプログラムです。このサンプルには、提供される 2 つのサービスがあります。

サービス情報は、info.plist に格納され、ログインの時にシステムによって読み込まれます。どのようにサービス情報を指定するかを調べるには、「Application Settings」を見てください。さまざまな項目についての詳細は、「Programming Topics」内の「Services」にあるドキュメントで見つけることができます。

SimpleService をインストールするには、「~/Library/Services」にそれをコピーし、ログアウトしてからログインしなおします。

サービスの作成において、気をつけなければならないいくつかの事柄があります。

2 アプリケーション

これはサービスアプリケーションなので、通常のように Xcode から直接サンプルを起動しません。上の説明にあるとおり、ビルドした後で作成されたファイルをシステム・ライブラリ・ユーザーのどこかの「~/Library/Services」にそれをコピーし、ログアウトしてから、ログインします。このサービスは、選択されたパス文字列で指定されたファイルを開いたり、選択された文字列を大文字化するので、テキストエディットアプリケーションなどのテキストエディタを起動し、パス文字列などを入力した後で、それを選択した状態で、アプリケーションメニュー(テキストエディットなら「テキストエディット」)から「サービス」サブメニューを表示させます。すると、以下のように、サービスメニューにこのサンプルのサービスが追加されているのがわかります。

パス文字列を入力して、それを選択し、「Open File」を選んでみます。すると、そのファイルが開かれます。また、「Readme.rtf」という文字列で「Capitalize String」を選べば、「Readme.Rtf」となります。

3 ファイル構成

では、プロジェクトファイルを見てみましょう。

ここで、注目するのは、SimpleService_main.m と、ServiceTest.h と .m、そしてInfo-SimpleService.plist です。通常のアプリケーションにある、nib ファイルが見当たらないことに注意してください。バッググラウンドで動作するアプリケーションなので、GUI の必要がなく、nib ファイルは必要ありません。

4 Info-SimpleService.plist

このファイルは、Xcode 上でテキストファイルとして開くこともできますし、Finder から開けば Property List Editor で開かれます。Xcode 上で開けば、以下のようになります。くわしい説明については、ガイド『システムサービス』の「サービスのプロパティ」を見てください。

Info-SimpleService.plist
... 略 ... <plist version="1.0"> <dict> ... 略 ... <key>NSBGOnly</key> // 1 バックグラウンド動作 <string>1</string> ... 略 ... <key>NSServices</key> // サービス定義 <array> // 配列 <dict> // 2 配列の最初の要素であるディクショナリ <key>NSKeyEquivalent</key> // ショートカット <dict> <key>default</key> <string>F</string> </dict> <key>NSMenuItem</key> // メニュー項目テキスト <dict> <key>default</key> <string>Open File</string> </dict> <key>NSMessage</key> // 呼び出すメソッド <string>doOpenFileService</string> <key>NSPortName</key> // ポート名 <string>SimpleService</string> <key>NSSendTypes</key> // 送信されるタイプ <array> <string>NSStringPboardType</string> </array> </dict> <dict> // 3 配列の2番目の要素であるディクショナリ <key>NSMenuItem</key> // メニュー項目テキスト <dict> <key>default</key> <string>Capitalize String</string> </dict> <key>NSMessage</key> // 呼び出すメソッド <string>doCapitalizeService</string> <key>NSPortName</key> // ポート名 <string>SimpleService</string> <key>NSReturnTypes</key> // 戻す値の型 <array> <string>NSStringPboardType</string> </array> <key>NSSendTypes</key> // 送信される型 <array> <string>NSStringPboardType</string> </array> </dict> </array> </dict> </plist>

他にもアプリケーションとしてのプロパティが宣言されていますが、このサンプルの説明に必要な部分だけを示しています。全体についてはプロジェクトファイルを見てください。

1 では、上の Readme.rtf で説明されているように、NSBGOnly キーでアプリケーションがドックに現れないようにするため、バッググラウンド動作が設定されています。デフォルトは 0 なので、通常のアプリケーションとして動作し、しかもサービスも提供するなら、このキーを設定する必要はありません。

次に、サービス用プロパティの記述が始まります。キー NSServices の値が配列であることに注意してください。このサンプルでは、1 つのアプリケーション内で 2 つのサービスを定義していて、この配列には 2 つの項目があります。両方ともディクショナリです。このように、提供する各サービスごとにディクショナリで、それぞれのサービスについての記述を行います。

最初のサービスの記述は、2 から始まります。まずショートカットが設定されています。サンプルをインストールしてわかるように、このメニューにはショートカットが表示されています。このショートカットは、アプリケーションと重複している場合は、アプリケーションが優先され、他のサービスと重複しているなら、どれが呼ばれるかはわかりません。自作サービスの場合、ここで設定せず、ユーザーに自由に設定してもらったほうがいいかもしれません。

次に、NSMenuItem キーで、メニュー項目のタイトルが設定されています。サブメニューを指定することもでき、ローカル化も可能です。ローカル化する場合、.strings ファイル内に、ここで設定されたメニュー項目のタイトルをキーとして指定します。

そして、NSMessage キーでサービスが実行される時に呼び出されるメソッド名が指定されています。その次は NSPortName キーによるポート名ですが、通常はアプリケーション名になります。そして、NSSendTypes キーで送信される型を指定しています。このサービスでは、戻り値がないので、送信される型だけが文字列として指定されています。

3 からは、もうひとつのサービスについての記述がなされています。基本的に上で説明したものと同じですが、ショートカットが設定されていない点、そして、返される型が設定されている点に注意してください。ここでは、どちらも文字列として設定されています。

5 SimpleService_main.m

このサンプルは、サービスに応答するだけの機能しか持っていません。実際のサービスに対して応答するメソッドは、ServiceTest クラスで実装されています。これをプロパティリストファイル内で探しても見つかりません。メソッド名は呼び出されるものとして設定されていますが、肝心のクラスも何も指定されていません。どうやってサービスを実行するメソッドを呼び出しているのでしょう。このプロジェクト内の SimpleService_main.m を見てみると、通常のアプリケーションの main.m とは違うことがわかります。たとえば、DotView サンプル内の main.m は次のようになっています。

DotView サンプル > main.m
#import <AppKit/AppKit.h> int main(int argc, const char *argv[]) { return NSApplicationMain(argc, argv); // AppKit 関数 }

この NSApplicationMainNSApplicationMain は、AppKit の関数リファレンスにおいて、「アプリケーションを作成し、アプリケーションの主要バンドルから主要 nib ファイルを読み込み、アプリケーションを走らせます。通常、アプリケーションの main 関数から一度だけこの関数を呼び出します(これは通常、Xcode によって自動的に生成されます)。」と書かれています。これが実行され、アプリケーションオブジェクトが作成されているだけです。実行ループなどは、このアプリケーションオブジェクトのコード内で行われています。

これに対して、SimpleService_main.m はもっと複雑です。

SimpleService_main.m
#import <Foundation/Foundation.h> #import "ServiceTest.h" int main (int argc, const char *argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // 1 自動解放プール作成 ServiceTest *serviceProvider = [[ServiceTest alloc] init]; // サービス提供インスタンス作成 NSRegisterServicesProvider(serviceProvider, @"SimpleService"); // 2 サービス提供インスタンス作成 NS_DURING [[NSRunLoop currentRunLoop] configureAsServer]; // サーバーとして設定 [[NSRunLoop currentRunLoop] run]; // 実行ループを開始 NS_HANDLER NSLog(@"%@", localException); // エラーの場合ログ出力 NS_ENDHANDLER [serviceProvider release]; // サービス提供インスタンス解放 [pool release]; // 自動解放プール解放 exit(0); // プロセスの終了状態が 0 であることを確実にする return 0; // ... そして main を ANSI 仕様にあうようにする }

1 では、自動解放プールを作成しています。これは、このサンプルが NSApplicationMain を使っていないからで、NSApplicationMain を使っていれば自動的に作成されているものです。Foundation または AppKit など、autorelease を使うフレームワークを使う場合、これが必要になります。

そして、サービス提供メソッドを定義しているクラスのインスタンスを作っています。それから、それを 2 で、NSRegisterServicesProvider 関数を使って、サービス提供オブジェクトとして登録しています。2 つめの引数は、上の Info-SimpleService.plist で、NSPortName キーの値として指定されたものです。これも NSApplicationMain を使っていないからで、通常のアプリケーションでサービスも提供する場合は、applicationDidFinishLaunching: 内で行います。くわしくは、『システムサービス』の「サービスの提供」の「サービス提供側としての登録」を見てください。

それから実行ループを開始します。開始する前に、現在の実行ループを configureAsServer でサーバーとして設定していることに注意してください。これはレシーバーをサーバープロセスによって使われるのに適したようにするために必要なすべての設定を行います。これらはエラー処理用のマクロ内に書かれていて、エラーが起こった場合ログが出力されるようになっています。

最後に、サービス提供インスタンスと自動解放プールを解放して、終了します。終了する前に状態が 0 で返るようにしています。自動解放プールは、ここで解放されるので、一時インスタンスを多量に使うサービスの場合、サービス提供メソッド内でさらに作成して、そのメソッドの終わりで解放するようにすれば、メモリ消費量を減らせるでしょう。

このように、バッググランウドで起動された後は、サービスを登録した後で実行ループが開始され、サービスの要求を待つループのなかに入って、バッググラウンドで待機し続けることになります。サービス登録の後、すぐに要求が来る可能性があるので、登録と実行ループ開始までの間には、できるかぎりメソッドを入れないほうがいいと思います。

6 ServiceTest クラス

さて、上のメイン関数内での登録と、プロパティリスト内の情報によって、サービスメニューが選ばれたとき、ServiceTest クラスのインスタンスのメソッドが呼ばれることになります。これらを見てみましょう。

6.1 クラス宣言

このサンプルはサービスに応答するだけなので、ヘッダファイルは非常に簡単です。

ServiceTest.h
@interface ServiceTest : NSObject @end

インスタンス変数もなく、継承だけが宣言されているだけです。

6.2 「Open File」サービス

プロパティリストの指定により、「Open File」サービスが選ばれれば、doOpenFileService: userData: error: メソッドが呼ばれることになります。このメソッドの形式は、サービス提供メソッドではどれでも同じで、「登録されたメソッド名: userData: error:」という形になります。

ServiceTest.m > doOpenFileService: userData: error:
- (void)doOpenFileService:(NSPasteboard *)pboard userData:(NSString *)data error:(NSString **)error { NSString *pboardString; NSArray *types; types = [pboard types]; // ペーストボード内のデータ型 if (![types containsObject:NSStringPboardType] // NSStringPboardTypeでない || !(pboardString = [pboard stringForType:NSStringPboardType])) { // 文字列を受けとれない *error = NSLocalizedString(@"Error: Pasteboard doesn't contain a string.", @"Pasteboard couldn't give string."); return; } if (![[NSWorkspace sharedWorkspace] openFile:pboardString]) { // ファイルを開けない *error = [NSString stringWithFormat: NSLocalizedString(@"Error: Couldn't open file %@.", @"Couldn't perform service operation."), pboardString]; return; } return; }

このメソッドは単純です。まず、ペーストボードに入れられている型の配列を取得します。次の if 文は、C 言語の条件式の評価を知ってないとわかりません。C 言語による条件の評価は、最初の評価が行われ、それで確定できないなら、次の評価という風に進みます。まず、その配列内に NSStringPboardType オブジェクトが含まれているかどうかがチェックされます。ないなら、すぐに下の文(error の設定)へと移ります。あるなら、この条件は否定されますが、|| 演算子なので、次の条件のチェックへと進みます。まず、評価を行うために、pboardString = [pboard stringForType:NSStringPboardType] が実行され、これにより、pboardString 内にペーストボードから NSStringPboardType 型のデータが入れられます。これが何らかの理由で失敗したら、pboardStringnil なので、それの否定であるこの条件は満たされます。よって、文字列の取得に失敗したら、下の文に移ることになります。文字列が取得できたら、この if 文には適合せず、{} 内は実行されず、次に進むことになります。

次の if 文でも、条件式内で、評価だけでなく、実際の動作が行われています。ワークスペースオブジェクトにファイルを開くように指示し、その戻り値をチェックしています。これが NO なら失敗なので、この if 文の条件は満たされることになります。開くのに成功すれば、そのまま戻ることになります。失敗すれば、error オブジェクトに文字列を設定しています。

6.3 「Capitalize String」サービス

もうひとつのサービスである「Capitalize String」サービスを見てみます。これはプロパティリストの設定により、doCapitalizeService: userData: error: が呼ばれることになります。

ServiceTest.m > doCapitalizeService: userData: error:
- (void)doCapitalizeService:(NSPasteboard *)pboard userData:(NSString *)data error:(NSString **)error { NSString *pboardString; NSString *newString; NSArray *types; types = [pboard types]; // ペーストボード内のデータ型 if (![types containsObject:NSStringPboardType] // NSStringPboardTypeでない || !(pboardString = [pboard stringForType:NSStringPboardType])) { // 文字列を受けとれない *error = NSLocalizedString(@"Error: Pasteboard doesn't contain a string.", @"Pasteboard couldn't give string."); return; } newString = [pboardString capitalizedString]; // 大文字化 if (!newString) { // 変換に失敗したら *error = NSLocalizedString(@"Error: Couldn't capitalize string %@.", @"Couldn't perform service operation."); return; } /* ここで大文字化された文字列を返す... */ types = [NSArray arrayWithObject:NSStringPboardType]; // 型の配列 [pboard declareTypes:types owner:nil]; // ペーストボードに対して型を宣言 [pboard setString:newString forType:NSStringPboardType]; // データを入れる return; }

前半は前のメソッドと同じです。NSStringcapitalizedString メソッドを使って、受けとった文字列を変換し、それから、それを返します。型の配列を作り、それをペーストボードに対して宣言し、それからペーストボードにデータを設定しています。

7 まとめ

サービスを作成すること自体は、それほど難しくないことがわかったと思います。このサンプルでは、バッググラウンドで動作するサービス専用アプリケーションであるため、追加の部分が多くありましたが、通常のアプリケーションを拡大してサービスに対応するなら、プロパティリストに設定し、applicationDidFinishLaunching: でサービスを登録し、あとはメソッドを実装するぐらいです。

このサンプルをさらに学ぶには、ローカル化などを行ってみるといいでしょう。また、このサンプルを離れて、通常のアプリケーションにサービス機能を追加してみるのも良いでしょう。


管理人:神吉 秀典 E-mail:puer@ba.wakwak.com