Objective-C ファイル読み込み 【初級編 第22回】

この記事は2013年2月14日に書かれたものです。内容が古い可能性がありますのでご注意ください。


ファイルの読み込み

ファイルからのデータ読み込み

前回はコマンドライン引数を使用して計算値を渡すようにしていました。そうすることで違う値で計算したいときに
わざわざコンパイルをやり直さなくてすみます。しかし、コマンドライン引数で何十人分ものデータを渡すのは現実的では
ありません。そこでコマンドラインからではなくファイルに書き込まれたデータを順番に読み込んでいって処理をさせるような
やり方に変更したいと思います。今回はそのファイル読み込みについての話となります。

ファイル読み込み

ファイルの読み込みに関してはすでに紹介したことのあるクラスに実装されています。「NSString」クラスの
ことです。
NSStringクラスに「initWithContentsOfFile」というメソッドが用意されていて、このメソッドは読み込んだファイルの
内容で文字列を作成します。
GNUStepでは以下のように定義されていました。

+ (id) stringWithContentsOfFile: (NSString*)path
                       encoding: (NSStringEncoding)enc
                          error: (NSError**)error;

最初の引数はファイルのパスを指定します。文字列を扱うNSStringはすでに紹介しましたのでここは問題ないと
思います。
第二引数にはNSStringEncoding型の引数を指定します。セレクタを見てるとencodingとキーワードが指定して
あることから想像がつく方もいると思いますが、ここで指定した文字コードでファイルから文字列を読み込みます。
NSStringEncoding型ですが以下のように宣言されています。

typedef enum _NSStringEncoding
{
/* NB. Must not have an encoding with value zero - so we can use zero to
   tell that a variable that should contain an encoding has not yet been
   initialised */
  GSUndefinedEncoding = 0,
  NSASCIIStringEncoding = 1,
  NSNEXTSTEPStringEncoding = 2,
  NSJapaneseEUCStringEncoding = 3,
  NSUTF8StringEncoding = 4,
  NSISOLatin1StringEncoding = 5,    // ISO-8859-1; West European
  NSSymbolStringEncoding = 6,
  NSNonLossyASCIIStringEncoding = 7,
  NSShiftJISStringEncoding = 8,
  NSISOLatin2StringEncoding = 9,    // ISO-8859-2; East European
  NSUnicodeStringEncoding = 10,
  NSUTF16StringEncoding = NSUnicodeStringEncoding,      // An alias
  NSWindowsCP1251StringEncoding = 11,
  NSWindowsCP1252StringEncoding = 12,   // WinLatin1
  NSWindowsCP1253StringEncoding = 13,   // Greek
  NSWindowsCP1254StringEncoding = 14,   // Turkish
  NSWindowsCP1250StringEncoding = 15,   // WinLatin2
以下長いので省略します。

} NSStringEncoding;

AppleのDeveloper LibraryにのっているものよりもGNUStepの方が定数が多くなっていました。
enumというのは列挙型のことで定数の集合体を作成するようなものです。
例えば「NSASCIIStringEncoding」という文字列に「1」という値を割り当てることで定数として使用できます。
第三引数ですが「Error」クラスが使用されています。このクラスはエラーの発生する可能性のあるメソッドで
エラーの情報を管理するために使用されるクラスです。
1つ目の引数と違って「*」が2つついています。1つであれば、ポインタ型となりオブジェクトのアドレスを格納するための変数と
いうことですが、もう1つついているのでオブジェクトのアドレスを格納する変数のアドレスということになります。
ポインタのポインタということになります。
NSErrorクラスの変数のアドレスを渡すことになります。

戻り値は読み込んだ文字列となります。

コードでの確認

では実際に変更しています。
main.mにファイル読み込みのコードを埋め込んでみます。

#import <Foundation/Foundation.h>
#import <Foundation/NSObject.h>
#import "Bmi.h"
#import "Person.h"
#import "Manage.h"

int main(int argc, const char *argv[]){

    Person *person = nil;
    int i;
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSError *error=nil;
    Manage *manage = [[Manage alloc] init];

    NSString *file = [NSString stringWithContentsOfFile:@"data.txt"
                 encoding:NSUTF8StringEncoding
                 error:&error ];

    NSArray *line = [file componentsSeparatedByString:@"n"];

    for(i = 0; i < [line count]; i++){
        if( [[line objectAtIndex:i] isEqualToString:@""]) {
            continue;
        }
        NSArray *col = [[line objectAtIndex:i] componentsSeparatedByString:@" "];
        person = [[Person alloc] initWithWeight:[[col objectAtIndex:1] doubleValue]
                                 height:[[col objectAtIndex:2] doubleValue]
                                 name:[col objectAtIndex:0]];
        [manage setPerson:person];
        [person release];
    }

    [manage showBestPerson];
    [manage release];
    [pool drain];
    return 0;
}

読み込むファイルをdata.txtとしてプログラムと同じ階層に配置します。
data.txt

Satoh 80 1.72
Suzuki 55 1.72
Naka 58 1.72

9行目でPerson型の変数を宣言し12行目でNSErrorクラスの変数を宣言しています。そしてそれぞれの変数にnilを代入しています。
空のオブジェクトということですね。
今回NSErrorオブジェクトは使用してはいませんが、使おうと思った時に値が返ってきていないとエラーがでてしまうため、
nilで初期化しています。
Personに関しても使用するときに値がないためエラーになるという状況を避けるために初期化しています。
nilにすることでどんなメッセージを送信しても無視されます。

15行目でstringWithContentsOfFileを使用してファイルから読み込んだ内容で、新しくNSStringオブジェクトを作成しています。
引数の1番目はファイルを指定しています。
引数の2番目ではUTF-8エンコーディングを表すNSUTF8StringEncodingにしています。
17行目でポインタのポインタを引数として渡す必要があるので変数アドレスを表す「&」をつけています。

このfileを表示させると内容がすべてでてきます。
そのままでは各種データが取り出せないので1行ずつに分割して値を取り出すための処理をいれています。

19行目のところで改行を区切りとして文字列を分割しています。以前に紹介していない「componentsSeparatedByString」と
いうメソッドを使用しています。このメソッドは引数で指定した文字列により分割し、分割された内容を
配列として返すものになっています。

21行目から29行目の処理で改行で区切った文字があればさらに「 」スペースで区切って名前、体重、身長を取り出し
その値を使用してPersonオブジェクトを作成し初期化しています。
21行目の処理は改行で区切られた文字列の個数分繰り返し処理をするようにしています。
22~24行目は文字列の内容が空だった場合は処理しないようにcontinueを使用して繰り返し処理をとばしています。
NSArrayに関しては
<Objective-C NSArray 配列 【初級編 第20回>を参照してください。

今回の処理では管理用のクラスに登録すればPersonオブジェクトはもう使用することがないので28行目で解放しています。
解放しないと繰り返し処理が行われ新しいPersonオブジェクトが作成されたときに元のPersonオブジェクトがのこったまま
となります。しかしそのオブジェクトにアクセスするためのアドレスが消えてしまっているので解放もできなくなります。

後の処理は前とかわっていません。

結果確認

結果を見てみましょう。
Objc22_1

ファイルから読み込まれたデータが表示されていますね。
こうしておけばデータが増えるとしても再コンパイルする必要がありません。

まとめ

今回のまとめとしては
・NSStringクラスのinitWithContentsOfFileメソッドを使用することでファイルの読み込みができる
・文字列をある区切り文字で分割するにはNSStringクラスのcomponentsSeparatedByStringを使用するとできる
・nilにメッセージを送っても無視されるので初期化に使用することがある
といったところでしょうか。

  • このエントリーをはてなブックマークに追加

PAGE TOP