【Swift】FileManagerでファイルを保存!操作方法や格納場所
この記事からわかること
- SwiftのFileManagerとは?
- 使い方や設定方法、メリット
- iOSのファイルシステム:サンドボックス構造とは?
- 格納できるディレクトリの種類
- ディレクトリ(フォルダ)を作成する方法
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
SwiftでファイルをiOSデバイス内に保存する方法をまとめていきたいと思います。
iOSアプリで端末(ローカル)にデータを永続的に保存する方法
Swiftではアプリ内からデータを永続的に保存する手段がいくつか存在します。
- テキストファイル
- UserDefaults
- Realm Swift(ライブラリ)
- Keychain
それぞれに一長一短がありますが、今回はFileManagerを使用した方法をまとめていきます。
Swiftでファイルを保存する際のポイント
Swiftでファイルを保存、読み込み、書き込みなどを行う際のポイントをまずは確認してみます。
ポイント
- サンドボックス構造
- Documentsディレクトリ
- URL型
- FileManager
アプリ内で生成したファイルなどを保存するにはiOSのファイルシステム構造やディレクトリの役割、FileManagerクラスの使い方が重要になってきます。
iOSのファイルシステム:サンドボックス構造
iOSのファイルシステムはサンドボックス構造が採用されています。サンドボックス構造とはサンドボックス(砂場)と呼ばれる外部とは隔離された仮想領域を用意し、その中でのみ動作や操作を許容する構造です。これにより外部の重要なプログラムなどへ悪意あるアクセスや動作が及ばないようにすることができます。
インストールしたiOSアプリはサンドボックス内に格納され動作しています。保存されたデータなどもサンドボックス内に格納され、アンインストール時に保存されたデータなども一緒に削除されます。
iOSアプリでファイルを保存する場合もサンドボックス内の決められたディレクトリ内に保存していきます。
おすすめ記事:【Swift】ファイルアプリからDocumentsフォルダへアクセス許可する方法!
参照できるディレクトリ
ファイルの保存先として使用できるのは下記のような構造になっているディレクトリ群のようです。それぞれに役割が決まっており、適切な場所へのファイルの格納が大事になってきます。Appleが定めているガイドラインに反したファイル管理をしているとアプリの審査がリジェクト(却下)されてしまうこともあるようです。
参考文献:FileSystem Programming Guide
├── AppData
│ ├── Documents
│ ├── Library
│ ├── Application Support
│ ├── Caches
│ ├── Preferences
│ ├── Saved Application State
│ └── SplashBoard
│ ├── SystemData
│ └── tmp
Documents
ユーザーが生成したファイルや画像や動画などのデータなど、ユーザーが閲覧するファイルを保存します。このディレクトリ内はiTunes/iCloudにバックアップされます。
Library
Documentフォルダに格納するデータ以外を保存するためのディレクトリです。ディレクトリの内容はLibrary/Cachesサブディレクトリ以外は、iTunesとiCloudによってバックアップされます。
tmp
テンポラリファイル(temporary files:一時的に作成されるファイル)が保存されるディレクトリです。tmp内のファイルは不要になったら削除することが推奨されています。またアプリが起動していないタイミングで自動で削除される可能性もあります。このフォルダはiTunes/iCloudにバックアップされません。
AppDataディレクトリを確認する方法
実際にAppDataディレクトリを確認するには少し手順があります。下記の記事を参考にしてください。
おすすめ記事:AppDataディレクトリを確認する方法
FileManagerクラスの使い方
class FileManager : NSObject
FileManagerとはファイルやディレクトリに関する操作(パスの取得や作成、削除、コピーなど)を行うことのできるクラスです。このクラスを使用することでローカルへのパス取得や、ファイルの保存や書き込み、ファイルの容量や更新日時の取得が可能になります。
FileManagerオブジェクトにはdefault
プロパティを介してアクセスします。
FileManager.default
サンドボックス内のディレクトリパスを取得する
各ディレクトリのパスを取得するにはurls
メソッドを使用します。引数for:
に取得したいパスの値を引数in:
には対象のアプリフォルダドメインを指し示す.userDomainMask
を指定します。返り値はオプショナル型の配列なのでfirst
で最初の要素を取得し、!
でアンラップすることでURL型のパスが取得できます。
Documentsディレクトリのパス
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
Libraryディレクトリのパス
FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!
Library/cacheディレクトリのパス
FileManager.default.urls(for: .cacheDirectory, in: .userDomainMask).first!
tmpディレクトリのパス
NSTemporaryDirectory()
Homeディレクトリのパスと応用
NSHomeDirectory() // Homeディレクトリのパス
NSHomeDirectory() + "/Documents" // Documentsディレクトリのパス
NSHomeDirectory() + "/Library" // Libraryディレクトリのパス
NSHomeDirectory()
を使って構築したディレクトリへのパスはString型
ですが、FileManager
を使って取得したパスはURL型
になります。
let str = NSHomeDirectory() + "/Documents"
let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
dump(type(of: str)) // String.Type
dump(type(of: url)) // Foundation.URL.Type
URL構造体とは?
Swiftで定義されているURL構造体はURLを扱うための構造体です。例えばString型をURL型にキャスト(型変換)したい場合はURL(string:)
形式のイニシャライザを使用することで変換できます。
let url : URL = URL(string:"https://www.amefure.com")!
URL
で囲うとオプショナルバリューになるので!
を使って強制的にアンラップしておきます。
ファイルの書き込み処理
引数に受け取ったテキストをDocumentsディレクトリ内の「sample.txt」に書き込み処理を行うwritingFile関数
を定義してみます。
func writingFile(_ text:String) {
// Documentsディレクトリまでのパスを生成
guard let docURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else{
fatalError("URL取得失敗")
}
// ファイル名を含めたフルパスを生成
let fullURL = docURL.appendingPathComponent("sample.txt")
do {
// 書き込み処理
try text.write(to: fullURL,atomically: true,encoding: .utf8)
} catch{
print("書き込み失敗")
}
}
FileManager
はまずdefault
でFileManagerオブジェクトを生成できます。さらにurls
で引数for:
に指定したパスを取得することができます。以下のようにコードを分割しても同意です。urls
で帰ってくるのはオプショナルバリューとなったURL型なのでアンラップしないといけない点に注意してください。
let fileManager = FileManager.default
let docURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first
// Optional(file:///〜
おすすめ記事:Optional(オプショナル)型とnilとは?
appendingPathComponent
はURLに追記処理ができます。これでDocuments/sample.txt
までのフルパスが完成します。write
で引数to
に指定したパスに書き込み処理を行えます。該当ファイルが存在しない場合は自動で生成して書き込み処理を行います。atomically
は書き込み中にエラーが発生してもファイルが壊れないように一時的なファイルを作成してくれます。
おすすめ記事:【Swift/String】write(to:atomically:encoding:)メソッドでファイルに文字列を書き込む方法!
おすすめ記事:【Swift】do-catchとthrows文の使い方!エラーハンドリングのやり方
ファイルの読み込み処理
Documentsディレクトリ内の「sample.txt」を読み込み内容を戻り値として返すreadingFile
関数を定義してみます。
func readingFile()->String?{
guard let docURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else{
fatalError("URL取得失敗")
}
let fullURL = docURL.appendingPathComponent("sample.txt")
do {
let textData = try String(contentsOf: fullURL, encoding: .utf8)
return textData
} catch {
return nil
}
}
ファイルの読み込み処理はString型
にキャストできるString()
で行うことができます。内部のイニシャライザーが引数contentsOf: URL
に指定されているURLのデータを取得し文字列として返してくれます。
ファイルの削除処理
Documentsディレクトリ内の「sample.txt」を削除するremoveFile関数
を定義してみます。削除処理はfileManager.removeItem
で実行できるのでfileManager
にFileManagerオブジェクトを格納して呼び出しやすくしておきます。
func removeFile(){
let fileManager = FileManager.default
guard let docURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else{
fatalError("URL取得失敗")
}
let fullURL = docURL.appendingPathComponent("sample.txt")
do {
try fileManager.removeItem(at: fullURL)
} catch {
print(error.localizedDescription)
}
}
ファイルのコピー処理
Documentsディレクトリ内の「sample.txt」をコピーするcopyFile関数
を定義してみます。コピー処理はfileManager.copyItem
で実行できます。引数にコピー元とコピー先のURL型のパスを渡すだけです。
func copyFile(){
let fileManager = FileManager.default
guard let atURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else{
fatalError("URL取得失敗")
}
guard let toURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else{
fatalError("URL取得失敗")
}
let fullAtURL = atURL.appendingPathComponent("sample.txt")
let fullToURL = toURL.appendingPathComponent("/test/sample.txt")
do {
try fileManager.copyItem(at: fullAtURL, to: fullToURL)
} catch {
print(error.localizedDescription)
}
}
空のファイルの作成処理
指定したパスに空のファイルを作成するならcreateFile
メソッドを使用します。引数atPath
にはString
型でファイルパスを渡します。contents
にはnil
を渡せば空のファイルが、何かしらのデータを渡せば書き込まれた状態のファイルが生成されます。
func createFile() {
let fileManager = FileManager.default
// String型のDocPathを作成
let docPath = NSHomeDirectory() + "/Documents"
let filePath = docPath + "/sample.txt"
if !fileManager.fileExists(atPath: filePath) {
fileManager.createFile(atPath:filePath, contents: nil, attributes: [:])
}else{
print("既に存在します。")
}
}
ファイルが存在するかどうか
指定したファイルパスにファイルがあるかどうかを識別するにはfileExists
メソッドを使用します。引数atPath
にはString
型でファイルパスを渡します。存在すればtrue
を返します。
先ほどの空のファイル作成処理ではファイルが既に存在する場合は作成処理を行わないようにしています。
if !fileManager.fileExists(atPath: filePath) {
fileManager.createFile(atPath:filePath, contents: nil, attributes: [:])
}else{
print("既に存在します。")
}
エラーの意味
The file “sample.txt” doesn’t exist.
// ファイル「sample.txt」は存在しません。
The file “sample.txt” couldn’t be opened because there is no such file.
// そのようなファイルがないため、ファイル「sample.txt」を開くことができませんでした
ディレクトリ(フォルダ)の作成
ディレクトリを作成するにはappendingPathComponent
メソッドを使用して、引数conformingTo
に.directory
を渡せばOKです。
func createDirectory(){
let fileManager = FileManager.default
let docPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let directory = docPath.appendingPathComponent("Test", conformingTo: .directory)
do {
try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil)
} catch {
print("失敗しました")
}
}
私がSwift UI学習に使用した参考書
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。