Cocoaでいこう! Macらしく 第14回
Yoshiki(DreamField)
この記事は、MOSAが発行するデベロッパ向けのデジタルマガジンMOSADeN 第74号(2003年7月15日発行)に掲載された記事です。2〜3ヶ月遅れで、ここに掲載して行きます。

今回から、再びTinyViewに手を入れて行きます。TinyViewのプロジェクトは捨てずに残していますでしょうか。捨てた方はいないと信じていますが(^^;)、途中から本連載を読みはじめた方のために、第9回が終わった時点のプロジェクトファイルを用意しました。必要な方は下記URLからダウンロードして下さい。

http://www.remus.dti.ne.jp/~yoshiki/cocoa/ed1/downloads/index.html

さて、現在のTinyViewは、ファイルを開いて表示できるだけです。ファイルを開けるようになったら、次の段階はやはり保存でしょう。今回は、開いたファイルを保存できるようにします。

ファイルを保存できるようにしよう

プロジェクトからTinyViewを起動して、何かJPEGファイルを開いて下さい。そして、Fileメニューを開いてみて下さい。「Save As...」が選べるようになっていると思います。これを選んでファイルを保存してみて下さい。シートパネルが出てきて、「Couldn't save document.」と表示されたと思います。これは保存の処理が実装されていないためです。裏を返すと、保存を呼び出す部分までは、既に実装されているということになります。

それでは、TinyViewを一度終了して、その中身を見てみましょう。MyDocument.mを開いてみて下さい。以下のメソッドがあると思います。

- (NSData *)dataRepresentationOfType:(NSString *)aType

コメントを読むと分かりますが、保存が必要な時にはこのメソッドが呼び出されますので、保存するファイルの内容をNSDataクラスのインスタンスで返してやれば、それがファイルに書き込まれるようになります。つまり、今回の場合はJPEGファイルの中身を入れて返せば良いわけです。なお、現在の雛形のままの状態では決め打ちでnilを返していますが、これはデータを返せないことを意味します。データを受け取る方では、nilが返って来るので、シートパネルを出して「Couldn't save document.」と表示しているというわけです。

では、プログラムの方針を立てましょう。今回は、どうやって保存するのかを見るのが目的ですから、ちょっと手を抜きまして、読み込んだデータをそのまま保存してしまうことにします。読み込んだデータはどうしていましたでしょうか。下記メソッドの中身を見て下さい。

- (BOOL)loadDataRepresentation:(NSData *)data ofType:(NSString *)aType

この中身の処理は、第8回で組みました。もう一度おさらいしますと、読み込んだデータはdataで渡って来ます。dataは、NSImageのインスタンスを生成する時に渡して、それっきりになっています。このdataは、自分で作ったオブジェクトでは無いので、自分では所有していません。ですから、おそらくは戻った後で消えてしまっていることでしょう。もちろん、実際にはどうなっているのか分かりませんが、少なくとも自分の中では捨ててしまっているのと同義です。そこで、これをとっておいて、保存する時には、これを返してあげることにします。

それでは、さっそく実装して行きましょう。dataを取っておくのは、retainして所有してしまえば良いだけですが、それだけではdataの所在を見失ってしまうので、そのアドレスをインスタンス変数に保存しておくことにします。まずは、そのためのインスタンス変数を宣言します。MyDocument.hを開いて下さい。下記の様に、NSDataクラスのインスタンスへのポインタを宣言します。名前はdocumentDataとします(6行目が、新しく加えた行です。)。

@interface MyDocument : NSDocument
{
    IBOutlet id imageView;

    NSImage *image;
    NSData *documentData;
}
@end

次にdataを所有する処理を加えましょう。MyDocument.mを開いて、loadDataRepresentation:ofType:に、下記の様にコードを追加して下さい(3行目と4行目が新しく加えた行です。)。

- (BOOL)loadDataRepresentation:(NSData *)data ofType:(NSString *)aType
{
    [ data retain];
    documentData = data;
    image = [ [ NSImage alloc] initWithData:data];
    return ( image != nil)?(YES):(NO);
}

まず、渡って来たdataをretainして所有します。その後、dataのアドレスをdocumentDataに格納します。これで読み込んだデータを所有し、いつでも使えるようになりました。それでは、dataRepresentationOfType:が返す値をこれに変更しましょう。以下の様にnilからdocumentDataに書き換えて下さい(3行目が書き換えた行です。)。

- (NSData *)dataRepresentationOfType:(NSString *)aType
{
     return documentData;
}

保存を行う時にはこのメソッドが呼ばれ、documentDataが返ります。これで、「Save As...」で保存が出来るようになりました。

でも、ちょっと待って下さい。これだけで良いのでしょうか?TinyViewは、Document-Based Applicationですから、複数のドキュメントを開いたり閉じたりを繰り返すことができます。ですから、ドキュメントを閉じたら、読み込んだデータは解放されなければなりません。そうでないと、開いたドキュメントの数だけ読み込んだデータが溜まっていってしまい、ドキュメントを閉じても、プログラムを終了するまで、それは解放されないことになってしまいます。これは、メモリリークです。

そこで、ドキュメントが解放される時に、documentDataも解放するようにします。MyDocument.mに、次のメソッドを追加して下さい。

- (void)dealloc
{
    [ documentData release];
    [ super dealloc];
}

deallocは、オブジェクトが解放される時に呼び出されるメソッドで、必ずどのオブジェクトも持っています。そこで、これをオーバーライドして、documentDataにreleaseメッセージを送る処理を加えます(3行目)。この時、最後に、スーパークラスにdeallocメッセージを送る処理を加えるのを忘れないようにしてください(4行目)。これを忘れると、解放の処理はここで終わってしまい、結局、ドキュメントは解放されないことになってしまいます。

これで、保存の処理は一応完成です。実際に動かして、「Save As...」で保存を行ってみて下さい。保存したら、そのファイルをダブルクリックしてみましょう。再び表示されましたでしょうか?え?表示されたけど、違うソフトで表示されてしまった?そうなのです。Cocoaで標準的な方法で組んだプログラムでは、Creatorをセットしないので、こうなってしまうのです。これではMacらしいプログラムとは言えません。そこで、次回はCreatorやTypeをセットする方法を説明します。

前頁目次次頁