【Swift】FileManagerでファイルを保存!操作方法や格納場所

この記事からわかること

  • SwiftFileManagerとは?
  • 使い方設定方法メリット
  • iOSファイルシステム:サンドボックス構造とは?
  • 格納できるディレクトリ種類
  • ディレクトリ(フォルダ)を作成する方法

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

SwiftでファイルをiOSデバイス内に保存する方法をまとめていきたいと思います。

iOSアプリで端末(ローカル)にデータを永続的に保存する方法

Swiftではアプリ内からデータを永続的に保存する手段がいくつか存在します。

それぞれに一長一短がありますが、今回はFileManagerを使用した方法をまとめていきます。

Swiftでファイルを保存する際のポイント

Swiftでファイルを保存、読み込み、書き込みなどを行う際のポイントをまずは確認してみます。

ポイント

アプリ内で生成したファイルなどを保存するには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ディレクトリを確認するには少し手順があります。下記の記事を参考にしてください。

おすすめ記事:AppDataディレクトリを確認する方法

FileManagerクラスの使い方

公式リファレンス: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構造体とは?

公式リファレンス: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学習に使用した参考書

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index