URL control

localのファイルへのURLを扱った経験より(2020-08-20)

畑田です。
OTOnectの開発においてlocal fileへのpathを取得し、内容を保存、取得する機会があってわかりづらかったので記録に残します。
全てコードで書いています。

absolute path, relative pathとは

そもそもパスとはあるファイルの所在を表す文字列です。
terminalなどのshellにコマンドを打つときも、$ cd Developer/GitHub/MyApp/みたいに入力すると思いますが、このスラッシュで区切られている文字列がパスです。英語で道です。
さらにパスには絶対パス(absolute path)と相対パス(relative path)の2種類があります。
絶対パスは所在を表したいファイルを最も浅い階層のディレクトリから表したもので、/Users/daikihatada/Developer/GitHub/MyAppのようなものです。
相対パスは所在を表したいファイルを、そのパスが書かれたファイルのディレクトリから表したもので、./Developer/GitHub/MyAppのようなものです。

URLとは

URL (uniform resource locator)は通信方式やファイルのネットワーク内の所在地、それ付随するその他の情報を表す文字列で、ブラウザーがウェブ上のリソースを一意に特定するための仕組みです。
query stringsやroute patameterを持つこともできます。

FoundationのURLにおけるURLの扱い

iOSでは、remoteだけでなく、localのファイルへのpathをURLとして表すことができます。
FoundationフレームワークURLは構造体であり、localファイルへの参照を持っています。
実際にlocalのディレクトリへのpathを取得し、URL typeで値を返す関数が存在します。
また、SwiftのURLクラスはそのURLの絶対パス相対パスを値(String type)として持つpropertyを持っています。
localのファイルを表すURLを用いると、そのファイルを直接操作することなどができます。
以下に、既存のURLを編集して新しいURLを生成するサンプルコードを載せておきます。

let path = "https://daikihatada-url/"
let directoryName = "image"
let imageName = "sample"
let imageExtension = "jpg"

// convert String into URL
let url = URL(fileURLWithPath: path) // https:/daikihatada-url

// append directory to the end of URL (with `isDirectory` `true`, `/` appended to the end of the directory name)
let directoryURL = url.appendingPathComponent(directoryName, isDirectory: true) //https:/daikihatada-url/image/

// append file to the end of URL
let imageNameURL = directoryURL.appendingPathComponent(imageName) // https:/daikihatada-url/image/sample

// append extension to the end of file name with `.`
let completeURL = imageNameURL.appendingPathExtension(imageExtension) // https:/daikihatada-url/image/sample.jpg

// delete extension at the end of URL
let extensionDeletedURL = completeURL.deletingPathExtension() // https:/daikihatada-url/image/sample

// delete file name behind the last slash
let imageNameDeleteURL = extensionDeletedURL.deletingLastPathComponent() // https:/daikihatada-url/image/

// get file name behind the last slash
let fileName = completeURL.lastPathComponent // sample.jpg

//  get extension of the file name behind the last slash
let fileExtension = completeURL.pathExtension // jpg

// get each component of URL in array
let components = completeURL.pathComponents // ["/", "daikihatada-url", "image", "sample.jpg"]

localのdirectory構造

アプリの詳しいdirectory構造の説明は省きますが下に参考画像を載せておきます。
本当はもう少しdirectoryがあります。

localのdirectory構造
localのdirectory構造

localのdirectoryへのURLを取得する方法

まずはDocumentsのdirectoryへのURLを取得してみます。
以下のコードは根っこのdirectoryの下から"Documents"という名前のdirectoryを探して、そこまでのURLを配列で返すものです。
for:の部分は、.cachesDirectoryなどにもできて様々なdirectoryを検索できます。
in:の部分は.allDomainMaskなどにもできて検索する範囲を指定できますが、.userDomainMaskを使うことがほとんどだと思います。

let documentsDirURLs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)

これの0番目の要素がアプリのホームディレクトリ直下のDocumentsディレクトリであるので、それを取得すればアプリのDocumentsディレクトリにアクセスできるというわけです。
一応、nil safetyを噛ませておくと安全ですね。

// with nil safety
guard let documentsDirURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }

ちなみに、配列に対してsomeArray[0]とすると、その要素がない場合にerrorを起こし、someArray.firstとすると、その要素がない場合にnilを返します。
値がnilであった場合にアプリを落としたい場合は前者を、optionalなどで処理を進ませたい場合は後者を選択するべきでしょう。
また、tmpディレクトリへのアクセスだけは特別です。

let tmpDirURL = FileManager.default.temporaryDirectory

tmpディレクトリのURLの取得方法が違うのはtmpディレクトリの特殊性に原因があるようです。
tmpディレクトリは一つのアプリケーションにただ一つしかなく、またその存在が保証されているため、tmpDirnilとなることはないと考えられるからだと思います。

localのdirectoryへのpathを取得する方法

以下はpathを直接取得するためのソースコードです。 ホームディレクトリとルートディレクトリとtmpディレクトリ以外は直接pathを取得できません。

// path to home directory
let homeDirPath = NSHomeDirectory()

// path to root directory
let rootDirPath = NSOpenStepRootDirectory()

// path to tmp directory
let tmpDirPath = NSTemporaryDirectory()

// url to documents directory
let documentsDirURL: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]

// path to documents directory
let documentsDirPath = documentsDirURL.path