2012年2月28日火曜日

データのディスクへの保存


データの保存先が決まれば、NSData、NSStringのメソッドでディスクへ保存することができます。保存メソッドは何種類かあり、以下はその一例です。

//NSDataの場合
BOOL result = [someData writeToFile:@"somePath" atomically:YES];

//NSStringの場合
BOOL result = [someString writeToFile:@"somePath" atomically:YES];

atomically:YESの場合、まず別名のファイルにデータを書き出し、エラーがなければ指定のファイル名にりネームします。既に同名ファイルが存在している場合、書き込み中も直前のデータが正常な状態で残り、エラーがあった場合も直前のデータが残ります。

iOSでは、原則としてアプリケーション毎に隔離された領域であるSandbox内にファイルを保存します。データの種類により保存先が異なり、システム上の扱いも異なります。
いくつかの項目でSandboxのディレクトリを分類してみました。

Sandbox内のディレクトリ
変更 永続性 iTunes
Backup
App
データ
User
データ
ファイル
共有(*6)
NSSearchPathDirectory
<Application_Home> △(*5) × NSApplicationDirectory
<Application_Home>/AppName.app x(*1)
<Application_Home>/Documents/ NSDocumentDirectory
<Application_Home>/Documents/Inbox x(*2) ×(*2)
<Application_Home>/Library/ NSLibraryDirectory
<Application_Home>/Library/Caches △(*3) × NSCachesDirectory
<Application_Home>/tmp/ ×(*4) ×
<Application_Home>/(独自ディレクトリ) △(*5) ×
*1 AppStoreからの購入時に同期が行われる。
*2 主にメールアプリが管理する添付データなど。管理アプリ以外はRead、Deleteのみ。
*3 ディスクスペース逼迫時に削除されることがある。iTunesバックアップ対象外。システム復元時は破棄される。
*4 古いファイルは削除されることがある。iTunesバックアップ対象外。システム復元時は破棄される。
*5 iTunesバックアップ対象外。システム復元時は破棄される。
*6 URL Schemaにより他のアプリからのアクセスが可能。未確認のためブランク

iOS 5以前の古いバージョンでは異なる場合があります。
参照:File System Programming Guide
        ファイルシステムプログラミングガイド

デスクトップアプリと比べ、iOSの場合はiTunes(およびiCloud)のバックアップ対象かどうかが判断基準に加わります。バックアップが不要な場合はDocumentsではなく、Library/Cacheか独自ディレクトが保存先になるでしょう。

Library/Cacheに保存したデータはディスク残容量が少なくなった場合に削除されることがあり得ますが、再現可能なデータであればこちらの方が望ましいでしょう。

tmp内のデータは古くなるとシステムも削除しますが、アプリがアクティブな時は削除されません。不要になったらアプリで削除すべきです。

保存先が決まればNSFileManagerとNSSearchPathDirectoryを使ってディレクトリのパスを取得します。

NSFileManager *fm = [NSFileManager defaultManager];
NSArray *dirs = [fm URLsForDirectory:directory
                           inDomains:NSUserDomainMask];
NSURL *dirURL = ([dirs count] > 0) ? [dirs objectAtIndex:0] : nil;
(directoryはNSSearchPathDirectory定数)

また、次のような関数が用意されています。
NSString *NSHomeDirectory() <Application_Home>ディレクトリ
NSString * NSTemporaryDirectory() <Application_Home>/tmp ディレクトリ

サブディレクトリの場合はNSURLのメソッドでディレクトリを追加します。

dirURL = [dirURL URLByAppendingPathComponent:@"subdir"];

ディレクトリがない場合は作成します。

BOOL isDirectory;
BOOL found= [fm fileExistsAtPath:dirURL.path isDirectory:&isDirectory];
if (!found) {
  BOOL success = [fm createDirectoryAtURL:dirURL withIntermediateDirectories:YES];
  if (!success) {
    //エラー処理
  }
} else if (!isDirectory) {
  //エラー処理
}

マルチスレッドに関して
NSData、NSStringによるデータ書き込みはお手軽ですが、 マルチスレッド の場合は競合に注意する必要があります。SOAPリクエストで使用したNSURLRequestも マルチスレッドで実行されます。このような場合、バックグランド処理ではファイルがない場合のみ新規ファイルの追加を行い、既存ファイルの変更・削除は行わないなどの取り決めをすれば競合を避けられます。競合が避けられない場合はNSFileCoordinatorなどによる対策が必要になります。

関連Blog
データのディスクへの保存
ディレクトリ作成/削除

2 件のコメント:

匿名 さんのコメント...

匿名で失礼します
UserデータとAppデータはどういった違いがあるのでしょうか?

Kenji Nakamura さんのコメント...

Appデータ:アプリケーションの設定や、ユーザーが直接操作せずアプリケーションが管理するデータなど。アプリケーションの実行モジュールも含まれる。
Userデータ:ユーザー入力に基づき作成したドキュメントファイルなど。
ファイルシステムプログラミングガイド」の「表 1-1 iOSアプリケーションの共通に使用されるディレクトリ」にどのようなデータを保存するディレクトリか書かれています。ディレクトリによりアクセス制限やiTunesへのバックアップ、メモリ逼迫時の削除の対象になるかどうかなど扱いが異なります。