🦅

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

2021/02/16に公開

こんにちは。
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

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

引き続き、今記事でもファイルの情報を取得することにしましょう。

ファイルの属性値を取得する

ファイルというものは作成されたときから様々なメタデータが付いています。
ここではそれらを「ファイルの属性値」と呼び、取得するための実装をしていきます。

iOSではファイルの属性といっても2つの情報が存在するようです。

attributesOfItem

attributesOfItem(atPath:)
Returns the attributes of the item at a given path.

与えられたパスにあるアイテムの属性を返します。

https://developer.apple.com/documentation/foundation/filemanager/1410452-attributesofitem

attributesOfFileSystem

attributesOfFileSystem(forPath:)
Returns a dictionary that describes the attributes of the mounted file system on which a given path resides.

与えられたパスが存在するマウントされたファイルシステムの属性を説明する辞書を返します。

https://developer.apple.com/documentation/foundation/filemanager/1411896-attributesoffilesystem

この2つの違いは別の記事に委ねるとして、いずれも[FileAttributeKey : Any]型の辞書で返されてきます。これらをマージしてファイルの属性情報として使っていきたいと思います。

extension File {
    
    var attributes: [FileAttributeKey : Any] {
        let attr1 = (try? FileManager.default.attributesOfFileSystem(forPath: path)) ?? [:]
        let attr2 = (try? FileManager.default.attributesOfItem(atPath: path)) ?? [:]
        return [attr1, attr2].reduce(into: [FileAttributeKey : Any](), { ret, attr in
            ret.merge(attr) { $1 }
        })
    }
}

ここで返されたファイルのメタデータを実際に覗いてみます。とりあえずどんなキーが辞書に入っているのでしょうか。

let file = File.documentDirectory + "hoge" + "fuga" + "sample.txt"
try? file.write(contents: "Hello")
file.attributes.forEach { kv in
    print(kv.key.rawValue)
}

// 結果
// NSFileExtensionHidden
// NSFileReferenceCount
// NSFilePosixPermissions
// NSFileCreationDate
// NSFileExtendedAttributes
// NSFileSystemFreeNodes
// NSFileSize
// NSFileSystemSize
// NSFileType
// NSFileSystemNodes
// NSFileModificationDate
// NSFileGroupOwnerAccountID
// NSFileSystemFileNumber
// NSFileGroupOwnerAccountName
// NSFileSystemNumber
// NSFileOwnerAccountID
// NSFileSystemFreeSize

色々と情報が取れてそうですね。
これらの情報から必要そうなものを抜き取りたいところですが、FileAttributeKeyを外から直接指定して取得するよりも、アプリ上必要な情報だけを取れるように隠蔽化しておいたほうがキレイかと思います。

ファイルの作成日時を取得する

まず、外から属性全体が見れないように隠蔽していきます。
その上でファイルの作成日時を返すプロパティを作ってやり、使用者に余計なことを考えさせないようにしてしまいます。

extension File {

    // 隠蔽化のためprivateに変更
-   var attributes: [FileAttributeKey : Any] {
+   private var attributes: [FileAttributeKey : Any] {
        let attr1 = (try? FileManager.default.attributesOfFileSystem(forPath: path)) ?? [:]
        let attr2 = (try? FileManager.default.attributesOfItem(atPath: path)) ?? [:]
        return [attr1, attr2].reduce(into: [FileAttributeKey : Any](), { ret, attr in
            ret.merge(attr) { $1 }
        })
    }
    
+   // 作成日時
+   var creationDate: Date? {
+       return attributes[.creationDate] as? Date
+   }
}

これでファイルの作成日時をDate型で取得することができます。

let file = File.documentDirectory + "sample.txt"
print(file.creationDate!)

// 結果:例
// 2021-01-01 12:00:00 +0000

こうしておいたほうが、属性値の辞書からの型キャストのことも考えずに直感的に使用できると思います。
他の属性についても同じようなノリで作っていくといいかと思います。

ファイルの更新日時を取得する

作成日時と同じことなのでソースコードだけ記しておきます。

extension File {

    // 隠蔽化のためprivateに変更
    private var attributes: [FileAttributeKey : Any] {
        let attr1 = (try? FileManager.default.attributesOfFileSystem(forPath: path)) ?? [:]
        let attr2 = (try? FileManager.default.attributesOfItem(atPath: path)) ?? [:]
        return [attr1, attr2].reduce(into: [FileAttributeKey : Any](), { ret, attr in
            ret.merge(attr) { $1 }
        })
    }
    
    // 作成日時
    var creationDate: Date? {
        return attributes[.creationDate] as? Date
    }
    
+   // 更新日時
+   var modificationDate: Date? {
+       return attributes[.modificationDate] as? Date
+   }
}

ファイルサイズを取得する

ファイルサイズはFileAttributeKey.sizeで取得することができますが、ドキュメントによると返される値はunsigned long longだとされています。swiftで扱う場合はUInt64になるので型には注意しましょう。

https://developer.apple.com/documentation/foundation/fileattributekey/1416548-size

extension File {

    // 隠蔽化のためprivateに変更
    private var attributes: [FileAttributeKey : Any] {
        let attr1 = (try? FileManager.default.attributesOfFileSystem(forPath: path)) ?? [:]
        let attr2 = (try? FileManager.default.attributesOfItem(atPath: path)) ?? [:]
        return [attr1, attr2].reduce(into: [FileAttributeKey : Any](), { ret, attr in
            ret.merge(attr) { $1 }
        })
    }
    
    // 作成日時
    var creationDate: Date? {
        return attributes[.creationDate] as? Date
    }
    
    // 更新日時
    var modificationDate: Date? {
        return attributes[.modificationDate] as? Date
    }
    
+   // ファイルサイズ
+   var size: UInt64 {
+       return attributes[.size] as? UInt64 ?? 0
+   }
}

使い方

let file = File.documentDirectory + "sample.txt"
try? file.write(contents: "Hello")
print(file.size)

// 結果
// 5

"Hello"しか書かれていないファイルなので、サイズは5バイトだけですね。

まとめ

ここまでで

  • ファイルの属性値(メタデータ)を取得できるようにする。
  • 属性値取得は隠蔽して、作成日時・更新日時・サイズを取得できるようする。

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

属性については他にも「隠しファイルかどうか」「プロテクトされているかどうか」なども取得することができます。
この記事では割愛しますが色々お試しください。

https://developer.apple.com/documentation/foundation/fileattributekey

ではまた。

Discussion