|
|
6. ファイル入出力
プログラムには継続性のあるものとないものがあります。継続性のないものとしては電卓プログラムなどが考えられます。電卓プログラムは起動するたびに計算をしてその結果が得られればそれでOKです。
(ただし電卓によっては最後の計算結果を保存しているものもあります)
それに対してプログラムに仕事をさせてその結果を保存しておき次回の起動時にはその保存された前回の結果から続きを始めたいものがあります。今回のアドレスブックなどはその典型でしょう。毎回起動する度にデータがなくなっていたらまったく意味がありません。
プログラムは基本的にすべての作業をメモリー上で行います。そしてメモリー上のデータはプログラムを終了するとすべて破棄されてしまいます。またコンピューターの電源を切ってもメモリー上のデータは破棄されてしまいます。プログラムで作成したデータをプログラム終了後やコンピューターをオフにした後でも残しておくにはデータをハードディスクなどにファイルとして保存しておかなければなりません。この作業のことをファイル入出力と呼びます。
Objective-C で用意されているファイル入出力 (ファイルの保存形式)
Objective-C には
1. テキストデータをテキストのまま保存し、テキストのまま読み込みをする
2. テキストデータを plist という Cocoa で採用された新しい形式で保存し読み込みをする
3. テキストデータおよびバイナリデータをバイアリデータとして保存し読み込みをする
という3つのファイル入出力形式が用意されています。
コンピュータのデータはテキストデータかバイナリデータの2種類にわかれます。テキストデータは言葉の通りにテキストとして人間も読めるデータです。バイナリデータはコンピュータにしか理解できない形のデータになります。バイナリとは2進法のことを表しています。
今回の Book プログラムでは3番目のバイナリデータでのファイル入出力を採用します。というか learn C の続きとして addressBook 構造体をベースにして Book クラスへと発展させたためにかなりピュアなモデルオブジェクトができ上がってしまい、バイナリデータでしかファイル入出力ができないためです。ファイル入出力の方法としてはこのバイナリデータで行う方法が一番本格的で、かつ一番難しい方法です。しかし頑張ってトライしましょう。
サンプルプログラム
Book.h
Book.m
main.m
Controller.h
Controller.m
コンパイルコマンドは
gcc main.m Book.m Controller.m -o book -framework Foundation -fobjc-gc-only -wall
です。
実行は
./book
です。
実行して終了すると、あなたの (デフォルトユーザーの) ホームフォルダの「ライブラリ」フォルダの Preferences フォルダに AABook.dat というファイルが出来ていると思います。
この新しいサンプルコードは
book6.zip
からダウンロードすることもできます。
データを入力してプログラムを終了しても、次回の起動時には終了前のデータが自動的に読み込まれます。つまりこの Book プログラムを起動してプログラム上ですべてのデータを削除するか、前述の AABook.dat ファイルを削除するか、もしくはコンピューターが壊れない限り登録されたデータは継続的に残ることになります。
Book.h Book.m (プロトコルの採用)
Book.h の3行目でスーパークラスの指定のあとに <NSCoding> という記述が追加されています。これをプロトコルの採用と呼びます。プロトコルとは汎用のメソッド群をオブジェクトに追加する方法です。例えばCDプレーヤー・MPプレーヤー・ビデオプレーヤー・DVDプレーヤーなどには再生・停止・巻き戻し・早送りという共通する機能があると思います。しかしそれは表面的なスイッチボタンとしては共通しているとしても実際に内部で行われる作業はまったく同じだとは言えないとします。もし再生・停止・巻き戻し・早送りという機能をもつクラスのサブクラスとしてCDプレーヤー・MPプレーヤー・ビデオプレーヤー・DVDプレーヤーを作ったとしても各機能(各メソッド)の実際の作業(メソッドの実装)は違うものとなります。このように多くのクラスで使われることが見込まれていても明らかに実装は違ってくるだろうと思われる機能については継承などの方法を使わずにこのプロトコルという方法を使ってオブジェクトにメソッドを追加します。
宣言
@protocol プロトコル名
メソッド宣言 ;
メソッド宣言 ;
...
@end
と記述してヘッダファイルとして保存しますが、この入門編でプロトコルをみずから宣言することはまずないと思います。既存のプロトコルは <Foundation / Foundation.h> ですでに読み込まれています。
採用
@interface クラス名 : スーパークラス名 <プロトコル名>
と記述します。interface 部のメソッドの宣言部分でプロトコルで採用したメソッドをあらためて宣言する必要はありません。interface 部の1行目で行った <プロトコル名> という記述は言わばメソッド宣言の変わりを行うものでありまたその役割しかありません。
Book.h での変更箇所は3行目に <NSCoding> を追加している点だけです。NSCoding プロトコルはこのプロトコルを採用したオブジェクトをコード化(バイナリデータに)するメソッドと、元のデータ形式に戻すメソッドの2つを持っています。
実装
プロトコルで採用したメソッドは @implementation 部で必ず実装しなければなりません。プロトコルの採用が @interface 部での単なるメソッド宣言であることを考えれば当然のことだと言えると思います。
NSCoding プロトコルでは次の2つのメソッドを実装しなければなりません。
- (void)encodeWithCoder:(NSCoder *)coder
- (id)initWithCoder:(NSCoder *)coder
Book.m ではこの2つのメソッドの実装を追加しています。
- (void)encodeWithCoder:(NSCoder *)coder では
[coder encodeObject:name forKey:@"BookName"];
で name インスタンス変数の値を forKey の名前、つまり BookName というコード名(目印)でコード化(encode)しています。
- (id)initWithCoder:(NSCoder *)coder では
name = [coder decodeObjectForKey:@"BookName"];
という具合に BookName というコード名でコード化されたデータを元の形式に戻して(decode) name インスタンス変数に代入しています。
この NSCoding プロトコルの2つのメソッドについても言えるのですが。コード化、シリアル化、アーカイブなどは同じことやだいたい同じことを言っていますが、そろそろ市販の解説書では何かを作ろうとした場合に十分に足りる説明まではしていません。紙面的な関係があるのだろうと思います。そしてこのあたりから自分で ADC (Apple Developer Connection) のマニュアルを読んでいくしか方法がなくなってきます。この第6回の最後か、あるいは別の章を設けて APPENDIX として参考図書や参考サイトの紹介を行おうかなと思っているのですが、結局最後は ADC のマニュアルを読まざるをえないと思います。残念ながら現段階では ADC のマニュアルはほとんどが英語のままで日本語に訳されているものはごくわずかです。また日本語に訳されていたにしても大変難しい内容です。とりあえず ADC のマニュアルやリファレンスの日本語化は非常に大事な問題だと思います。
main.m
40行目
[ books writeFile ];
main.m の変更点はこの1行を足している点だけです。このメッセージ式は booksという変数に格納されている Controller オブジェクトの writeFile メソッドを呼び出しています。writeFile メソッドはこの第6回で Controller クラスに新たに付け加えられたメソッドで books 配列のデータをファイルに保存します。
Controller.h
8行目
インスタンス変数として NSString *bookFile ; を追加しています。
17行目
- ( BOOL ) readFele ; というメソッドの宣言を追加しています。
18行目
- ( void ) writeFile ; というメソッドの宣言を追加しています。
Controller.m
writeFile メソッド 144行目〜149行目
データを保存するためのメソッドです。この Controller.m に書かれている最初のメソッドは readFile という保存されたデータを読み込むためのメソッドですが先に保存メソッドから説明したほうが分かりやすいと思います。なお Controller.m には8つのメソッドが書かれていますがどのメソッドから書かなければいけないという順序はありません。C 言語の関数とプロトタイプの関係と同じです。すべてのメソッドは Contoroller.h (ヘッダファイル)もしくはスーパークラスで定義済みです。従って実装の順序を気にする必要はありません。
146行目 [ NSKeyedArchiver archiveRootObject : books toFile : bookFile ];
NSKeyedArchiver クラスにはオブジェクトブラフをアーカイブしてファイルに書き出すクラスメソッドとファイルからオブジェクトを復元するクラスメソッドが用意されています。用語説明をすると。
オブジェクトグラフ
ツリー構造(枝分かれ構造)になった複数のオブジェクトの固まり
アーカイブ Archive
encode (データ化) とほぼ同じ意味。しかし encode の1つ上の階層から各encode(複数オブジェクトのコード化)をまとめているという感じ。decode (データの復元)をまとめるのをアンアーカイブ Unarchive と呼びます
上記の archiveRootObject : toFile : メソッドは第一引数のオブジェクトをルートとするオブジェクトグラフをデータ化して第二引数で指定されたファイルに書き出します。books オブジェクトは配列オブジェクトです。books オブジェクトをルートとしてその各要素のオブジェクトも含めて丸ごとコード化しています。なおコード化するには各オブジェクトが NSCoding プロトコルを実装してしている必要があります。このことを準拠と呼びますが NSArray および NSMutableArray は始めから NSCoding プロトコルに準拠しています。
readFile メソッド 8行目〜19行目
最初にNSFileManager のインスタンスのポインタを fmanager 変数に格納して fileExistsAtPath メソッドで引数 bookFile に確かにファイルが存在しているか確かめています。NSFileManager はファイルのやりとりを管理するクラスです。そしてもしファイルが存在していれば books 配列に
books = [ NSKeyedUnarchiver unarchiveObjectWithFile : bookFile ] ;
NSKeyedUnarchiver のクラスメソッド unarchiveObjectWithFile の引数で指定されたファイルからコード化されたデータを復元して books に代入しています。そして YES を返しています。ファイルが存在していない場合には何もせずに NO を返しています
init メソッド 21行目〜33行目
23行目 このメソッド内で使う局所変数 home を宣言しています。
26行目 NSHomeDirectory( ) は Objective-C に用意されている関数です。現在マックを使っているユーザーのホームフォルダまでのパスを NSString 文字列で返します。
27行目 home に代入されたデフォルトユーザーのホームフォルダまでのパス( NSString 文字列)に stringByAppendingString : というインスタンスメソッドで引数の /Library/Preferences/AABook.dat という NSString 文字列を追加した新しい文字列を作っています。そしてでき上がった文字列で bookFile インスタンス変数を初期化しています。
28行目 自身の readFile メソッドを呼び出しています (正確にはメッセージを送っています) 。if 文の条件式が not 演算子で否定されていますので [ self readFile ] が NO を返した場合、つまりデータファイルが存在しなかった場合は 30行目の books = [ NSMutableArray array ] ; が実行され books インスタンス変数に要素数ゼロの NSMutableArray のインスタンスが代入されます。なお 29行目が空いているのは単なる私のタイプミスです。意味はありません。このように if 文の実行式(実際に行われる式・文)がひとつだけの時にはブロック文 { } は省略することができます(省略しなくても構いません)。
お疲れさまでした。learn ObjC 2.0 はこれで一旦終了いたします。読んで頂いてありがとうございました。もしかすると APPENDIX として参考図書や参考サイトの紹介を行うかもしれません。宜しくお願いいたします。
|
|
|