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

3 min read読了の目安(約3500字

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

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

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

iOSアプリ開発では、永続的データやキャッシュのためにテキストや画像や動画等のファイルを取り扱うことも多いと思います。
標準ではファイル操作のためのFileManagerというクラスが用意されていて、実装者はシングルトンオブジェクトである FileManager.default を参照し、そのメソッドを使用していきます。

基本的には操作する(または参照する)ファイルの絶対パスを引数にして処理を行わせますが、ここが冗長になってしまうソースコードをたびたび見ることがあります。
パス文字列とファイル操作自体が分かれていることは責務の分離としては間違いではないとは思いますが、これらの実装をもっとシンプルに書けるようにして開発を楽にしていきたいものです。

そこで今回用意するのが File という構造体です。

File構造体の基本定義

File 構造体は、ファイルの絶対パスである path を定数に持つシンプルな構造体にします。

import Foundation

struct File {
    let path: String
}

定数を1つ持つだけの構造体ですから、インスタンスを作るときも

let file = File(path: "path/to")

このようにシンプルです。

この構造体に様々な機能を拡張していきます。

特定のパスをすぐ参照できるようにする

iOSアプリではファイルの置かれる場所はある程度限定されます。
そこでそのディレクトリパスは定数で定義しておくといいと思います。

extension File {
    
    static let documentDirectoryPath = NSSearchPathForDirectoriesInDomains(
        .documentDirectory,
        .userDomainMask,
        true
    ).first!
    
    static let libraryDirectoryPath = NSSearchPathForDirectoriesInDomains(
        .libraryDirectory,
        .userDomainMask,
        true
    ).first!
    
    static let temporaryDirectoryPath = NSTemporaryDirectory()
    
    static let mainBundlePath = Bundle.main.bundlePath
}

とりあえず、よく使うであろうディレクトリのパスを4つ定数定義しました。

詳しくはこちらを参照

https://developer.apple.com/icloud/documentation/data-storage/index.html

パス文字列が定義できるとFile構造体を作ることができます。File構造体自体もこのように定数定義しておくと何かと便利です。
(※Fileという名前ですが、ディレクトリの抽象型にもなるというわけです)

extension File {
    
    static let documentDirectoryPath = NSSearchPathForDirectoriesInDomains(
        .documentDirectory,
        .userDomainMask,
        true
    ).first!
    
    static let libraryDirectoryPath = NSSearchPathForDirectoriesInDomains(
        .libraryDirectory,
        .userDomainMask,
        true
    ).first!
    
    static let temporaryDirectoryPath = NSTemporaryDirectory()
    
    static let mainBundlePath = Bundle.main.bundlePath

+   static let documentDirectory = File(path: documentDirectoryPath)
+   static let libraryDirectory = File(path: libraryDirectoryPath)
+   static let temporaryDirectory = File(path: temporaryDirectoryPath)
+   static let mainBundle = File(path: mainBundlePath)
}

使い方

let dir = File.documentDirectory

// または
let dir: File = .documentDirectory

これでドキュメントディレクトリへの参照が簡単かつ直感的にできるようになりました。

パスの連結を簡単にする

File構造体の中身はパス文字列だけです。このパス文字列にディレクトリ名やファイル名を足していくことになります。
ここではその方法を簡単にするため以下のように "+"演算子で行えるようにしておきます。

extension File {
    
    func append(pathComponent: String) -> File {
        return File(path: (path as NSString).appendingPathComponent(pathComponent))
    }
    
    static func + (lhs: File, rhs: String) -> File {
        return lhs.append(pathComponent: rhs)
    }
}

構造体なので、元の構造体のパス文字列を変更するのではなく、パス文字列を変更した新しい構造体を返していることがミソです。

使い方

let file = File.documentDirectory + "hoge" + "test.txt"

これで (ドキュメントディレクトリ)/hoge/test.txtというパス文字列を持ったFile構造体を簡単に作れます。

同じファイルかどうかを判定できるようにする

同じパス文字列を持つFile構造体は"同じファイルである"といえると思うので、Equatableに準拠させてやりましょう。
Equatableに準拠すると、比較がしやすい上に、配列操作などでも強力な機能を持つことができます。

extension File: Equatable {
    
    static func == (lhs: File, rhs: File) -> Bool {
        return lhs.path == rhs.path
    }
}

まとめ

ここまでで

  • File構造体を作る
  • 特定のディレクトリをすぐ参照できる
  • パス文字列を+演算子で連結できる
  • Equatableに準拠する

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

これだけだと、まだそんなに便利ではありません。
次回以降でもう少し使い勝手をよくしていきましょう。

ではまた。