🦅

[iOSアプリ開発] ファイルを抽象化した構造体を作る(6)

2021/02/19に公開

こんにちは。
ZennではiOSアプリ開発の普段自分が使っているちょっとしたTipsなどを書いていければと思っております。

今回はSwiftでは少し冗長になりがちなファイルの操作を簡単にするために "ファイルを抽象化した構造体" を作っていきます。
当記事だけで終わりというわけではなく、続き物にしていく予定なのでよろしくおねがいします。

前回まではこちら
https://zenn.dev/nkysyuichi/articles/467ddcc1041d4e
https://zenn.dev/nkysyuichi/articles/7269df712386ed
https://zenn.dev/nkysyuichi/articles/2c44e971f7afac
https://zenn.dev/nkysyuichi/articles/bcc71f9eeabf1f
https://zenn.dev/nkysyuichi/articles/0689c909bcffb4

ファイルを取り扱う構造体

File構造体はディレクトリを抽象化する構造体でもあります。ディレクトリということは、その中身もあります。

ディレクトリの内容を確認するために用意されているのはFileManagercontentsOfDirectory(atPath:)です。しかし、このメソッドは指定したパスが存在しなかったり、ファイルだった場合には例外を投げます。

過去の経験から、この例外に対して例外処理を都度書くよりはシンプルに空配列が返ったほうが使い勝手がよかったです。

ディレクトリ内の内容を高階メソッドで処理できるようにする

まず、プライベートメソッドとして疑似map関数のような高階メソッドを用意します。

extension File {
    
    private func filesMap<T>(_ transform: (String) throws -> (T)) rethrows -> [T] {
        guard let fileNames = try? FileManager.default.contentsOfDirectory(atPath: path) else {
            return []
        }
        return try fileNames.map { try transform($0) }
    }
}

メソッドのシグネチャが若干ややこしいですが、map同様に関数ブロックの戻り値がそのまま配列の型として認識され返却されるようにしています。

関数ブロックにはディレクトリ内のファイル名(またはディレクトリ名)が渡されるので、それをよしなに使う側が加工して型を決めた上で返却するだけでいいというわけです。

ちなみに最後の行は

return fileNames.sorted().map { try transform($0) }

にしてやると、Finderで並ぶ順のようになります。

では、これを用いて便利なパブリックプロパティを作っていきましょう。

ディレクトリ内のファイルをすべて取得する

ディレクトリの中身をFile構造体の配列として返すことで、続くその後の処理も扱いやすいようになります。

extension File {
    
+   var files: [File] {
+       return filesMap { self + $0 }
+   }
    
    private func filesMap<T>(_ transform: (String) throws -> (T)) rethrows -> [T] {
        guard let fileNames = try? FileManager.default.contentsOfDirectory(atPath: path) else {
            return []
        }
        return try fileNames.map { try transform($0) }
    }
}

前述のfilesMapの使いやすさがこれで分かると思います。

これでディレクトリの中がすべてFile構造体で取得できるようになります。

ディレクトリ内のファイルパス、ファイル名をすべて取得する

それ以外にfilesMapを使って、ファイルパスやファイル名を返すプロパティもこんな感じで作れます。

    var filePaths: [String] {
        return filesMap { (self + $0).path }
    }
    
    var fileNames: [String] {
        return filesMap { $0 }
    }

工夫次第ではディレクトリ内のファイル容量取得なども作れそうですね。

ファイル構造体をデバッグ時に見やすくする

さて、ディレクトリ内のファイルがすべて取れるようになったところで、違う話です。

アプリのローカルディレクトリ内でファイルを操作するようになると、今現在どんなファイルが置かれているかを確認したくなります。

しかし、File構造体をそのままprintすると、フルパスがコンソールに吐き出されてしまい、少し見づらいものになってしまいます。

そこで、デバッグ時にそれが見やすくなるようにCustomStringConvertibleに準拠してコンソールに吐き出される文字列をカスタマイズしてしまいましょう。

extension File: CustomStringConvertible {
    
    var description: String {
        let type = isDirectory ? "Dir" : "File"
        return "<\(type) \(name)>"
    }
}

これはあくまで一例なので、自分の見やすいようにしてください。

まとめ

  • ディレクトリ内のファイル(またはディレクトリ)をすべて取得できるようにした
  • 高階メソッドにより実装がシンプルにでき、ループ回数も節約できる
  • すべて取得したときのデバッグの見やすさのためにCustomStringConvertibleに準拠した

というわけで、ディレクトリ内操作もこんなにシンプルにできるようになりましたね。

それでは、また。

Discussion