ファイル入出力とかについて

今更な感じがしますが、iPhone でのファイル入出力について調べてみたので、足跡を残しておきます.

ま、それほど大それたもんじゃないですね...

サンドボックス

iPhone では、一部例外を除いてアプリケーション毎に定められた箇所にしかアクセスできない.
(一部例外に当たるのは、アドレス帳とフォトライブラリ)

iPhoneアプリケーションには、次のようなフォルダ構成が割り当てられる.

+ Applications
  + $(MyApp_HOME)         ... 自身のアプリケーションに割り当てられたホームディレクトリ
    - MyApp.app           ... アプリケーション本体
    + Documents           ... ドキュメントフォルダ
    + Library             ... ライブラリフォルダ
      + Preferences       ... 初期設定フォルダ
    + tmp                 ... テンポラリーフォルダ
  + $(OtherApp_HOME)
    + Documents
    + Library
    + tmp

すなわち、基本的には $(MyApp_HOME) 以下のファイルならば自由にアクセスすることが可能だが、他の
アプリケーションフォルダにアクセすることはできない.
('+' はフォルダ、'-'はファイルとして表している.ただし、厳密には、アプリケーション本体は
バンドルと呼ばれるフォルダ(のようなもの)の点に注意すること)

$(MyApp_HOME) に割り当てられるフォルダの実名称は、アプリケーション名で決定されるわけではなく、
アプリケーション毎に割り当てられるIDによって決定される.

このため、適正なファイルのパスは、APIを通じて取得する必要がある.

アプリケーションインストール時に、Documents、Library、tmp フォルダが生成されるが、どのフォルダに
何を入れるべきかは、アプリケーション実装者が決定すればよい.

注意点として、iPhone が iTune と同期のする際には tmp フォルダの同期が行われない点に注意する必要がある.

また、Documents フォルダは iTune と同期の際にバックアップが取られるので、アプリケーション自身が
下手なバックアップ処理を考える必要はないそうだ.
(未検証だが、開発者セミナーでそんなことを言っていた記憶がある)

ファイルのパスの取得方法

サンドボックス領域にアクセスを行うため、パスを取得しなければいけない.
HOME ディレクトリへのパスを取得する方法は、次のようになる

    NSString* a_home_dir = NSHomeDirectory();

ホームディレクトリのパスが取得できてしまえば、あとは文字列の操作で希望するファイルのパスを生成する
ことができる.

    NSString* a_home_dir = NSHomeDirectory();
    NSString* a_doc_dir = [a_home_dir stringByAppendingPathComponent:@"Documents"];
    NSString* a_file_path = [a_doc_dir stringByAppendingPathComponent:@"doc-image.jpg"];

また、Documents ディレクトリへのパスを取得するためには、次の API を利用することもできる.

NSArray* a_paths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES );

このあたりは、好みで決めてしまえば良いと思う.

シミュレータ上でアプリケーションを動作させた場合、アプリケーションフォルダは次の場所に作成される.

/Users/${USER_NAME}/Library/Application Support/iPhone Simulator/User/Applications/${APP_ID}/

注意点としてアプリケーションのIDは、起動毎に変わってしまう可能性があることに注意する必要があるかもしれない.
(ってか、基本的には変化する)
ただし、アプリケーションのIDは変わっていても、書き込んだファイル/フォルダは引き継がれる.
(フォルダ名のリネームが行われているだけか?)

テンポラリフォルダへのパスを取得するためには、次の API を利用することもできる.

    NSString* a_tmp_dir = NSTemporaryDirectory();

シミュレータで実行した場合、テンポラリフォルダのパスは次のようになる

/var/folders/oY/oY8FvzwjHnW4y6epxlKa6p70YSw/-Tmp-/doc-image.jpg

実機上では、"/Applications/${APP_ID}/tmp" になると思うのだが...
このテンポラリフォルダは、シミュレータ上で動作する場合には、起動毎にパスが変わらない.

ファイルの書き込み

NSData のような、ObjectC 基底の型であれば、次のように書き込みが行える.

    NSData* a_data;
    ... // a_data にデータを設定するコード
    [a_data writeToFile:a_image_file atomically:YES];

C言語でいうところの FILE* の様にファイルを取り扱いたければ、FileHandle を利用すればよい.
ただし、ファイルが存在していない場合には、 FileManager を用いてファイルを新規作成する必要がある.

    NSData* a_data;
    ... // a_data に値を設定するコード
    NSString* a_home_dir = NSHomeDirectory();
    NSString* a_doc_dir = [a_home_dir stringByAppendingPathComponent:@"Documents"];
    NSFileManager* a_file_mgr = [NSFileManager defaultManager];
    [a_file_mgr changeCurrentDirectoryPath:a_doc_dir];

    // ファイルが存在しなければ作成する
    if( [a_file_mgr fileExistsAtPath:@"image.jpg" isDirectory:&a_isDir] == NO ){
        [a_file_mgr createFileAtPath:@"image.jpg" contents:a_data attributes:nil];
    }else{
        // 存在していれば上書き
        NSFileHandle* a_file = [NSFileHandle fileHandleForWritingAtPath:a_file_path];
        [a_file writeData:a_data];
    }

FileHandle を用いれば、書き込み/読み込みのカーソルを操作しながらファイルのアクセスを行うことが可能になる.

ファイルの読み込み

ファイルの書き込みと同様、NSFileHandle を用いればよい.

ディレクトリの作成

NSFileManager を利用すると、ディレクトリの作成なども行える.

    // images ディレクトリが存在しない場合には作成
    BOOL a_isDir = NO;
    if( [a_file_mgr fileExistsAtPath:@"images" isDirectory:&a_isDir] == NO){
        [a_file_mgr createDirectoryAtPath:@"images" attributes:nil];
    }

ディレクトリ作詞時には、NSDictionary を用いた属性設定がおこなえるようだが、どうやって指定するのかよく分からない.
(どこを見れば書いてあるんですかね?教えて偉い人)

nilを指定しておくとデフォルトの属性で作成が行えるようなので、当面はこれで問題ないと思われる.

おわりに

サンドボックスとか、パスの取得の方法とかが分かってしまえば、後は NSFileManager / NSFileHandle あたりの
ヘルプを漁っていけばやりたいことは大体できると思います.