タイトル今から始めるCocoaプログラミング》ドラッグ&ドロップでファイルを開く(4)Pure Javaとの組み合わせカテゴリーJava, Cocoa, 今から始めるCocoaプログラミング
作成日2001/5/10 17:52:36作成者新居雅行
「ドラッグ&ドロップでファイルを開く」の一連のシリーズの締めくくりとして、Pure Java、つまりJavaの標準ライブラリをベースに作られたソフトウエアとの連携について考えてみたい。ただし、いろいろと不明な点もあり、また筆者の理解不足な点もあるかとは思うが、問題点などがあれば是非とも指摘していただきたい。
この一連のシリーズでは、CocoaのフレームワークにあるDelgateというメカニズムを使って、Cocoaアプリケーションで、Finderからのドラッグ&ドロップの機能を組み込むということを説明してきた。こうしたドラッグ&ドロップは、Mac OSのアプリケーションではどうしても組み込みたい機能であることはいまさら力説する必要もないだろう。しかしながら、Pure Javaのアプリケーションでこのドラッグ&ドロップを組み込むには、MRJのライブラリ機能を使って実現する必要がある。だが、筆者がためしたところでは、Pure Javaのアプリケーションで、MRJの機能を使ってドラッグ&ドロップに対応する機能を組み込んでも、ドラッグ&ドロップで既定のメソッドが呼び出されることはなかった。おそらくこれはバグだとは思われるが、Mac OS X 10.0.3でも直っていない。しかしながら、処理プロセス部分がPure Javaで作ったものがあり、そのプログラムをどうしてもドラッグ&ドロップ対応で作りたかったということもあり、Cocoaアプリケーションの中で、PureJavaのクラスを使って処理をするということが可能かを試してみた。とりあえずは何とか作成することができたのであるが、やはりいろいろと修正は必要になった点も含めて、「やったらこうなった」的な内容になってしまうがその状況をお届けしたい。もっとも、MRJがきちんと機能するようになると、こうした手法も意味がないという気もするのだが、Cocoaアプリケーションという外枠を用意しておくと、Pure JavaのプログラムからCocoaの機能を手軽に利用できるといったメリットもあるかもしれない。

Cocoaアプリケーション部分は、前回までに説明したものとほぼ同じようなものだ。ドラッグ&ドロップに対応し、ダブルクリックとドラッグ&ドロップで処理を切り分けるように作っているのがCocoaの部分だ。そして実際の処理部分はPure Javaで作ってある。ただし、ここでのPure Javaは、ビジュアル階層はまったく使っていないで、ファイル処理だけである。Cocoaのアプリケーション内でSwingを使ったらちゃんと動くのか…という点も興味はあるところだけども、これはまた別の機会に試してみたい。今回紹介するのは、ユーザインタフェースのないアプリケーションとなっている。
作成方法としては、Project BuilderでCocoa-Java Applicationを選択して、nibファイルでFile’s Ownerを自分で作ったクラスにDelegateしておくというだけである。そのプロジェクトに、Pure Javaのクラスで作ったソースを追加しておく。実はそれだけでおおむね動いてしまうのである。ビルドする上では何も考えなくても、CocoaのプロジェクトにPure Javaクラスがあってもかまわないわけだ。

だが、実際に動かすと、まずはファイルのエンコードの問題に引っかかった。JavaのString型クラスでは、文字列を一律にUNICODEで扱う。一般にはJavaではシステムのエンコードに従って処理をするため、たとえばShift-JISで記述されたファイルの内容を読み込むときには、Shift-JISの文字列を自動的にUNICODEに変換してString型として得られる。また、逆も同様だ。だから、ファイルのエンコードについてはあまり考えなくてもいいと言えばいいわけだ。しかしながら、Cocoaのプロジェクトの中で、java.io.FileWriterやjava.io.FileReaderあるいはその継承クラスを使うと、基本的にはコードの変換がされないと考えた方が良い。Shift-JISのファイルの内容はそのままのコードでStringに入ったりする。読んだ結果をそのまま書き出せば、あたかもうまく動いているかのように見えるので、ちょっとはまったが、結論的には読み書きするときには、エンコードをプログラムで記述するというのがもっとも確実な方法だと思われた。そのためには、java.io.InputStreamあるいはjava.io.OutputStream系のクラスを使う方が、byte型配列でデータ処理ができて都合がいいということもある。システムプロパティのfile.encodingを使って指定ということもあるのだが、TextEditの動作を見ても、ユーザが保存するファイルのエンコードを指定するという機能が組み込まれている。どうしても、エンコードを意識したファイル処理ということは、Mac OS Xでは避けては通れないと思われる。
ただ、処理の都合上、1行ずつ読み込むjava.io.LineNumberReaderは便利だから使いたいと考えるかもしれない。その場合は、readLineでいったんString型に読み込むが、getBytesメソッドでいったんバイト列にそのまま分解し、さらに新たにnew Stringで文字列を作るときに、1つ目の引数にバイト列、2つ目の引数に「"JISAutoDetect"」を指定して、それなりにうまく処理できると言うことも見つけた。文字列処理が重なるので処理速度が気になるところだが、こういった方法もとりあえずは使えるようである。

次にひっかかりがあったのが、メモリ管理の部分だ。CocoaはObjective-CのライブラリをJava-Bridgeという機能でJavaのクラスとして見えるようにしているというのがおおまかなところであり、実際には背後ではObjective-Cの枠組みで動いている。特にメモリ管理の点では大きな違いがある。Objective-Cのメモリ管理については以下の文書に詳しく記述されている。

◇Programming Topic: Memory Management
 http://devworld.apple.com/techpubs/macosx/Cocoa/TasksAndConcepts/ProgrammingTopics/MemoryMgmt/index.html

Javaではほとんど何も考えなくてもよかったに近いメモリ管理であるが、CocoaのJavaでも多くの場合はそうなると、Java-Bridgeのドキュメントには解説されている。つまり、CocoaのプログラムをJavaで書く場合には、基本的にはJavaのやり方でいいということである。
ところが実際に作り込んでいくうちに、メモリ関連のエラーで落ちるようになってしまった。フリーズのようになったり、あるいはエラーメッセージを出すなど症状は一定しないのだが、まずはどうすれば回避できるかを探ってみた。次のような状況でエラーとなることがわかった。Interface Builderでインスタンス化したクラスにおいて、メソッド外で定義してあるフィールド変数で、初期化としてnewでクラスのインスタンスを生成した結果を代入しているたである。もちろん、いくつかのメソッドでそのインスタンスを使いたいからフィールド変数で定義したのだが、こうすると、どうもエラーが出て落ちたりする。ある日出てきていたエラーメッセージからの想像でしかないのだが、そうして確保したオブジェクトのインスタンスは、Objective-Cのautoreleaseによってオートリリースプールを使ってインスタンスが保持されているようなのである。ところが、Interface Builderでインスタンス化したオブジェクトの初期化の段階では、何らかの理由でオートリリースプールが使えないということをにおわせるメッセージが出ていた。(実は再現しようとしたら、そのメッセージは出ないで落ちるだけになったりと、なかなか確認できなかった。)
それでどのように回避したかと言えば、フィールド変数で参照していたものを、メソッドの中に入れて、メソッドを呼び出すたびに、インスタンス化するようにしたら、エラーはでなくなった。1回のメソッド呼び出しで、オブジェクトを使い捨てするような流れにすれば、とりあえずは問題なくなったので、その方法で対処している。
なお、フィールド変数の初期化でオブジェクトの生成をしたらダメかというとそうでもない。別途プロジェクトを作って、短いプログラムで試してみたけども、その場合はとりあえずはエラーは出なかった。どういった条件で上記のようになるのかは残念ながら不明である。

以上はたまたま筆者が遭遇した引っかかりであり、問題解決というほどのものでもないのではあるが、何かの手がかりになるかとも考えて記事にした。「今から始めるCocoaプログラミング」の「ドラッグ&ドロップでファイルを開く」のシリーズは、とりあえずはこれで締めくくりたい。
関連リンク