iOS でファイル保存、読み込み

環境: Swift2.0

アプリ内にデータを保存する場合、どこに保存するのかを調べた。
だいたい以下のディレクトリ(またはこれらの中に作成したサブディレクトリ)のどれかに保存することになるようだ。

  • Documents/
  • Library/
    • Library/Application Support/
    • Library/Caches/
  • tmp/

保存するデータの内容によって使い分ける必要がある。
Apple のガイド
https://developer.apple.com/jp/documentation/FileSystemProgrammingGuide.pdf
から引用する。

Documents/

ユーザが生成したデータを保存するために使います。ファイル共有の機能により、ユーザはこのディレクトリ以下にアクセスできます。したがって、ユーザに見せても構わないファイルのみ置いてください。 このディレクトリの内容はiTunesによってバックアップされます。

ユーザデータはDocuments/以下に置いてください。これは一般に、ユーザに積極的に見せるファイルです。ユーザが自由に作成、インポート、削除、編集する対象です。たとえば描画アプリケーションの場合、ユーザが作成するグラフィックファイルがこれに当たります。テキストエディタであればテキストファイルが該当します。動画/音声アプリケーションの場合、ユーザが後で試聴するためにダウンロードしたファイルもこれに当たります。

Documents/およびApplication Support/以下のファイルは、自動的にバックアップの対象にな ります。NSURLIsExcludedFromBackupKeyキーを指定して -[NSURL setResourceValue:forKey:error:]を実行することにより、バックアップ対象から除外できます。いつでも再生成またはダウンロードできるファイルは、この対象から外してください。大容量のメディアファイルの場合、これは特に重要です。ダウンロードした動画や音声は、バックアップ対象に含めないようにしてください。

Library/

  • Library/Application Support/
  • Library/Caches/

これは、ユーザのデータファイル以外のファイル用の最上位ディレクトリです。通常、標準的なサブディレクトリを用意し、いずれか適当な場所に保存します。iOSアプリケーションは通常、Application Support およびCachesというサブディレクトリを使いますが、独自のサブディレクトリを作成しても構いません。 ユーザに見せたくないファイルはLibraryサブディレクトリ以下に置いてください。ユーザデータのファイル保存用に使ってはなりません。 Libraryディレクトリの内容は、Cachesサブディレクトリ以下を除き、 iTunesによるバックアップの対象になります。

データキャッシュファイルはLibrary/Caches/ディレクトリ以下に置きます。キャッシュデータは、一時データよりは長期間にわたって残しておきたいけれども、補助ファイルほどではない場合に有用です。一般にキャッシュデータは、なくてもアプリケーションの動作に影響しませんが、性能改善の効果が期待できます。例として、データベースのキャッシュファイルや、一時的でいつでもダウンロード可能なデータなどがあります。なお、ディスク空間を確保するため、システムがCaches/ディレクトリを消去することがあるので、必要ならばいつでも生成し直し、あるいはダウンロードできるようになっていなければなりません。

tmp/

このディレクトリは、アプリケーションを次に起動するまで保持する必要のない一時ファイルを書き込むために使用します。不要になったファイルは削除しなければなりません。もっとも、アプリケーションが動作していないときに、システムがこのディレクトリ以下をすべて消去することがあります。 iTunesはこのディレクトリの内容をバックアップしません。

一時データはtmp/ディレクトリに置いてください。一時データとは、長期間にわたって保存しておく必要がないデータのことです。使い終わったら削除して、デバイス上の空間を消費し続けないようにしなければなりません。システムは、アプリケーションが動作していない間、定期的にここにあるファイルを消去します。したがって、一度停止した後、ファイルが残っていることを前提として処理してはなりません。

まとめると以下のようになる。

Documents/
・ユーザが作成したデータ(テキスト、写真)、再作成が不可能なデータなど重要なデータを保存するディレクトリ。
・ユーザに見せたくないファイル(設定ファイルとか)はここに置かない。
・iTunes、iCloud バックアップ対象。

Library/
・ユーザのデータファイル以外を保存するディレクトリ。自分でサブディレクトリを用意して保存する。
・ユーザに見せたくないファイルはここに置く。
・iTunes、iCloud バックアップ対象(Caches ディレクトリを除く)。

Library/Caches/
・一時的なデータを保存するディレクトリ。システムが自動削除する可能性がある。
・後から再度ダウンロードして復旧可能なデータを置くこと。
・iTunes、iCloud バックアップ対象外。

Library/Preferences/
・アプリの設定を保存するディレクトリ。NSUserDefaults のデータはここに保存される。

tmp/
・一時的なデータを保存するディレクトリ。アプリが動作してないときにシステムが自動削除する可能性がある。
・iTunes、iCloud バックアップ対象外。
・使い終わったらアプリ側で削除して容量削減に務める。

tmp/Library/Caches/の使い分けについて

どちらのディレクトリも一時ファイルの保存先として使うが、システムによるファイル削除のタイミングが違う。

ディレクト 非アクティブ中のシステムによる削除 アクティブ中のシステムによる削除 ガイド記載の使用例
tmp/ × 次のアプリの起動で保持する必要のないファイル
Caches/ ダウンロードコンテンツ

ディレクトリパスの取得方法

NSSearchPathForDirectoriesInDomains関数を使う。
またはNSHomeDirectory()でホームディレクトリを取得してそれに連結してもよい。

Documentsディレクト

let path: String = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
print(path)
// /Users/xyk/Library/Developer/CoreSimulator/Devices/B9D56604-82C8-4752-A4D8-51292D8F625A/data/Containers/Data/Application/73FBC4AB-EA27-4ECF-A1FA-9389A25DD2CD/Documents

// または
let path: String = NSHomeDirectory() + "/Documents"

// または
let path: NSURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]

Libraryディレクト

let path: String = NSSearchPathForDirectoriesInDomains(.LibraryDirectory, .UserDomainMask, true)[0]
print(path)
// /Users/xyk/Library/Developer/CoreSimulator/Devices/B9D56604-82C8-4752-A4D8-51292D8F625A/data/Containers/Data/Application/7A7E5AA5-9ED9-4CE3-8559-90A946227DF8/Library

// または
let path: String = NSHomeDirectory() + "/Library"

// または
let path: NSURL = NSFileManager.defaultManager().URLsForDirectory(.LibraryDirectory, inDomains: .UserDomainMask)[0]

Library/Cachesディレクト

let path: String = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true)[0]
print(path)
// /Users/xyk/Library/Developer/CoreSimulator/Devices/B9D56604-82C8-4752-A4D8-51292D8F625A/data/Containers/Data/Application/3959DFC8-25A6-47C4-B246-FE8304B17864/Library/Caches

// または
let path: String = NSHomeDirectory() + "/Library/Caches"

// または
let path: NSURL = NSFileManager.defaultManager().URLsForDirectory(.CachesDirectory, inDomains: .UserDomainMask)[0]

Library/Application Supportディレクト

let path: String = NSSearchPathForDirectoriesInDomains(.ApplicationSupportDirectory, .UserDomainMask, true)[0]
print(path)
// /Users/xyk/Library/Developer/CoreSimulator/Devices/B9D56604-82C8-4752-A4D8-51292D8F625A/data/Containers/Data/Application/9E25C963-2F4B-4985-BA9B-B9DADF52E9EC/Library/Application Support

// または
let path: String = NSHomeDirectory() + "/Library/Application Support"

// または
let path: NSURL = NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask)[0]

tmp/ディレクト

let path: String = NSTemporaryDirectory()
print(path)
// /Users/xyk/Library/Developer/CoreSimulator/Devices/B9D56604-82C8-4752-A4D8-51292D8F625A/data/Containers/Data/Application/6572ECC7-D57A-40C0-8E0E-E52AC8F64012/tmp/
// NSHomeDirectory()と違ってパス末尾にスラッシュが含まれる。~/tmp/


ディレクトリ・ファイルの操作

NSFileManagerによる作成・削除

NSFileManagerクラスを使う。
よく使いそうな操作メソッドの定義。

// NSFileManager

// 存在チェック
public func fileExistsAtPath(path: String) -> Bool

// ディレクトリ作成
public func createDirectoryAtPath(path: String, withIntermediateDirectories createIntermediates: Bool, attributes: [String : AnyObject]?) throws

// ファイル作成
public func createFileAtPath(path: String, contents data: NSData?, attributes attr: [String : AnyObject]?) -> Bool

// 削除
public func removeItemAtPath(path: String) throws

NSDataをファイル書き込み

ファイルパス(StringまたはNSURL)を指定して NSData として書き込む。

// NSData
public func writeToFile(path: String, atomically useAuxiliaryFile: Bool) -> Bool
public func writeToFile(path: String, options writeOptionsMask: NSDataWritingOptions) throws

public func writeToURL(url: NSURL, atomically: Bool) -> Bool
public func writeToURL(url: NSURL, options writeOptionsMask: NSDataWritingOptions) throws

NSDataとしてファイル読み込み

ファイルパス(StringまたはNSURL)から NSData として読み込む。

// NSData
public init?(contentsOfFile path: String)
public init(contentsOfFile path: String, options readOptionsMask: NSDataReadingOptions) throws

public init?(contentsOfURL url: NSURL)
public init(contentsOfURL url: NSURL, options readOptionsMask: NSDataReadingOptions) throws

UIImageをファイル書き込み

UIImageUIImagePNGRepresentationでNSDataに変換してから書き込み。

UIImagePNGRepresentation(image)?.writeToFile(imagePath, atomically: true)

UIImagePNGRepresentation(image)?.writeToURL(imageURL, atomically: true)

UIImageとしてファイル読み込み

ファイルパスから UIImage として読み込む。

// UIImage
public init?(contentsOfFile path: String)

NSURLを使ってローカルファイルを読み書きする場合の注意点

文字列のファイルパスをNSURLに変換して扱う場合、fileURLWithPath:を使うこと。

let url = NSURL(fileURLWithPath: NSTemporaryDirectory() + "dummy.txt")
// 以下は正しくない
// let url = NSURL(string: NSTemporaryDirectory() + "dummy.txt")