【Swift】URL で特定のディレクトリやファイルを指す

これで、URLを使って操作するファイルやディレクトリを指していきますが。


func appending<S>(
    component: S,
    directoryHint: URL.DirectoryHint = .inferFromPath
) -> URL where S : StringProtocol

👉 appending(component:directoryHint:) | Apple Developer Documentation hatena-bookmark

一つ目の引数の末尾の「/(スラッシュ)」で、

そのURLが指しているものが、

「ディレクトリなのかファイルなのか」

が変わる。

実体ではなく、指しているもの。

指す側の認識。


let home = URL.homeDirectory

let documents = URL.documentsDirectory
let documents1 = home.appending(component: "Documents/")
let documents2 = home.appending(component: "Documents", directoryHint: .isDirectory)

let documents3 = home.appending(component: "Documents")
let documents4 = home.appending(component: "Documents/", directoryHint: .notDirectory)

print(
  documents == documents1,  // true
  documents == documents2,  // true
  documents1 == documents2, // true

  documents == documents3,  // false
  documents == documents4   // false
)

2つ目の引数 directoryHint は、その指定を上書きする。

デフォルトは .inferFromPath

楽になったような、逆に混乱するような、

どうなんだろ。



【Swift】URL appendingPathComponent() vs appending(component:)

どれを使ったらいいのか分かりづらかったので歴史。

 

🤔 iOS 8.0-17.5 Deprecated


func appendingPathComponent(_ pathComponent: String) -> URL

👉 appendingPathComponent(_:) | Apple Developer Documentation hatena-bookmark


func appendingPathComponent(
    _ pathComponent: String,
    isDirectory: Bool
) -> URL

👉 appendingPathComponent(_:isDirectory:) | Apple Developer Documentation hatena-bookmark

 

🤔 iOS 14.0+


func appendingPathComponent(
    _ partialName: String,
    conformingTo contentType: UTType
) -> URL

👉 appendingPathComponent(_:conformingTo:) | Apple Developer Documentation hatena-bookmark


UTType.plainText 
UTType.text  // markup
UTType.utf8PlainText

👉 UTType | Apple Developer Documentation hatena-bookmark

 

🤔 iOS 16.0+ (2022-09リリース 2年前 現シェア9割)


func appending<S>(
    component: S,
    directoryHint: URL.DirectoryHint = .inferFromPath
) -> URL where S : StringProtocol

👉 appending(component:directoryHint:) | Apple Developer Documentation hatena-bookmark


case checkFileSystem
case inferFromPath // default
case isDirectory
case notDirectory

👉 URL.DirectoryHint | Apple Developer Documentation hatena-bookmark


Bundle.main.bundleURL.appending(component: "hogehoge.file", directoryHint: .notDirectory)

👉 [Swift] パフォーマンスが気になる場面ではappendingPathComponentの使い方に注意する hatena-bookmark

 

🤔 まとめ

まずは、


appending(component: "newfile.txt")

からでお願いします。

こういう歴史的経緯系が混乱します。

 

🤔 参考

👉 Foundation URL Improvements - Development / Core Libraries - Swift Forums hatena-bookmark
👉 [Back from revision] Foundation URL Improvements - Development / Core Libraries - Swift Forums hatena-bookmark
👉 🚀 iOS version Market Share hatena-bookmark


【Swift】その URL が ファイル なのか ディレクトリ なのか 存在しないのか

これはディレクトリ。


URL.documentsDirectory

では、これは何を指しているのか。


URL.documentsDirectory.appending(component: "xxx")

ファイルなのか、ディレクトリなのか、存在しないのか。


appendingPathComponent(_:) が Deprecated なので、

👉 appendingPathComponent(_:) | Apple Developer Documentation hatena-bookmark

appending(component:directoryHint:) としています。

👉 appending(component:directoryHint:) | Apple Developer Documentation hatena-bookmark

 

🤔 ディレクトリなのか


print(
  URL.documentsDirectory
    .appending(component: "xxx")
    .hasDirectoryPath
)

// false

👉 hasDirectoryPath | Apple Developer Documentation hatena-bookmark

「ディレクトリではない」のですが、

そのURLにファイルがあるのか、

または、何も存在しないのか、

分かりません。

 

🤔 URL.resourceValues(forKeys:) を使う

👉 resourceValues(forKeys:) | Apple Developer Documentation hatena-bookmark

この形でよく使われています。


extension URL {
  var isDirectory: Bool {
    (try? resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == true
  }
}


print(
  URL.documentsDirectory
    .isDirectory,
  URL.documentsDirectory
    .appending(component: "xxx")
    .isDirectory
)

// true false

extension 内の条件式の左辺は、


URL先にリソースが存在してディレクトリのとき
→ true

URL先にリソースが存在してディレクトリでないとき
→ false

URL先にリソースが存在しないとき 
→ nil

となります。

URLの指すリソースが、ファイルとディレクトリのみであるとすれば、以下のように書くことができますね!


extension URL {
  private var resourceIsDirectory: Bool? {
    (try? resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory
  }

  var exists: Bool {
    resourceIsDirectory != nil
  }

  var isFile: Bool {
    resourceIsDirectory == false
  }

  var isDirectory: Bool {
    resourceIsDirectory == true
  }
}


let documents = URL.documentsDirectory

print(
  documents.exists,
  documents.isDirectory,
  documents.isFile
)
// true true false

let documentsXXX = URL.documentsDirectory
  .appending(component: "xxx")

print(
  documentsXXX.exists,
  documentsXXX.isDirectory,
  documentsXXX.isFile
)
// false false false

 

🤔 まとめ

URLResourceValues が扱う値はいろいろです。

👉 URLResourceValues | Apple Developer Documentation hatena-bookmark

以下に、まとめておきます。

そもそもは、

「ファイルの存在の確認時に、制限のゆるい String に置き換えてからの FileManager.fileExists(atPath:) を使うのが面倒すぎる。」

ということがきっかけでした。

👉 fileExists(atPath:) | Apple Developer Documentation hatena-bookmark

実体とURLは直感と違います。

👉 【Swift】URL で特定のディレクトリやファイルを指す hatena-bookmark