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

4 min読了の目安(約3600字TECH技術記事

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

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

前回まではこちら

https://zenn.dev/nkysyuichi/articles/467ddcc1041d4e
https://zenn.dev/nkysyuichi/articles/7269df712386ed
https://zenn.dev/nkysyuichi/articles/2c44e971f7afac

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

今記事ではファイル自体の情報を取得することにしましょう。

ファイル名を取得する

File構造体では絶対パスの文字列だけが保持されています。
しかし、ここからファイル名を取りたいという気持ちになると思います。

パス文字列から名前に関する情報はNSStringの機能を使うことで取ることができます。
これをFile構造体でラップしてやります。

ここに4種類の名前を取れるようにしました。

extension File {
    
    var name: String {
        return (path as NSString).lastPathComponent
    }
    
    var `extension`: String {
        let ext = (name as NSString).pathExtension
        return ext.isEmpty ? "" : ".\(ext)"
    }
    
    var extensionWithoutDot: String {
        let ext = (name as NSString).pathExtension
        return ext.isEmpty ? "" : "\(ext)"
    }
    
    var nameWithoutExtension: String {
        return (name as NSString).deletingPathExtension
    }
}

name

純粋なファイル名です。

extension

ファイル名から拡張子だけを返します。このとき拡張子はドットが付いた状態になります。
「拡張子」を意味するextensionという英単語は予約語なのでバッククォートで囲ってやります。

extensionWithoutDot

extensionはドット付きの拡張子でしたが、こちらはそれを取り除いています。

nameWithoutExtension

ファイル名から拡張子部分を取り除いた名前です。

使い方

let file = File.documentDirectory + "hoge" + "fuga" + "sample.txt"
print(file.name)
print(file.extension)
print(file.extensionWithoutDot)
print(file.nameWithoutExtension)

// 結果
// sample.txt
// .txt
// txt
// sample

ファイルかどうか、ディレクトリかどうか

プログラマブルにファイル操作をしていると、今扱っているFile構造体がファイルを指しているのか、ディレクトリを指しているのかの判定が必要になってくるシーンもあるでしょう。

その判定のためのプロパティを追加していきたいと思います。

ファイルかどうか

extension File {
    
    var isFile: Bool {
        var isDirectory: ObjCBool = false
        if FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) {
            return !isDirectory.boolValue
        }
        return false
    }
}

以前にもファイル存在確認のためにFileManager.default.fileExists()は行いましたが、それに加えてディレクトリかどうかの判定を引数に参照渡しすることができます。

この仕組みを使うことで「そのパスは存在する」かつ「それはディレクトリではない」つまり「ファイルである」という判定をすることができますね。

では、ドキュメントディレクトリが空っぽの状態で以下のように実装してみます。

let file = File.documentDirectory + "hoge" + "fuga" + "sample.txt"
print(file.isFile)
try? file.write(contents: "Hello")
print(file.isFile)
// 結果
// false
// true

書き込みが行われるとファイルとして存在していることになるのでtrueが返ります。

ディレクトリかどうか

さて、ファイルかどうかを判定できるのですから、それがfalseだったらディレクトリだろうというのは間違った判定になります。「そのパスは存在する」かつ「それはディレクトリである」つまり「ディレクトリである」という判定にしておかなくてはなりません。

では、

extension File {
    
    var isDirectory: Bool {
        return exists && !isFile
    }
}

というふうに定義しても良いかなと思うのですが、isFileに依存しない作りにしたかったので

extension File {
    
    var isDirectory: Bool {
        var isDirectory: ObjCBool = false
        if FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) {
            return isDirectory.boolValue
        }
        return false
    }
}

ちょっと冗長ですが、isFileと同じように実装をしました。こちらはお好みで選択ください。

同じようにドキュメントディレクトリが空っぽの状態で以下のように実装してみます。

let file = File.documentDirectory + "hoge" + "fuga" + "sample.txt"
print(file.parentDirectory.isDirectory)
try? file.write(contents: "Hello")
print(file.parentDirectory.isDirectory)
print(file.isDirectory)
// 結果
// false
// true
// false

書き込みが行われると同時にディレクトリも作られるのでtrueが返ります。
変数file自体は書き込まれたファイルを指すのでもちろんfalseになります。

まとめ

ここまでで

  • ファイル名と拡張子、またはその組み合わせを取得できるようにしました。
  • そのFile構造体が「実在するファイルなのか」を取得できるようにしました。
  • そのFile構造体が「実在するディレクトリなのか」を取得できるようにしました。

というところまでやりました。

次回以降でもファイルの色々な情報を取れるようにしていきましょう。

ではまた。