更新於 2022 年 09 月 27 日
# 取得寫入檔案的 URL
在應用程式執行的時候,有的時候會需要將檔案存放使用者的裝置中。我們只要將資料轉成 Data,便能使用它的方法.write(to: URL)
來寫入,不過,這個 URL 該從何而來?
在不同的裝置,我們能讀取、寫入的資料夾位址都有所不同,所以會需要先透過 API 來取得資料夾目錄 URL,接著再組合成我們要寫入的 URL。
# 取得資料夾 URL
URL 靜態屬性
iOS16 新增了方便又可靠的取得可用的資料夾位址的方式。你可以直接透過 URL 的靜態屬性 來取得想要資料夾位址。
如果你不確定要選哪一個的話,以下是幾個常用的資料夾路徑。
// 希望使用者看到的資料放這
URL.documentsDirectory
// 不希望使用者看到的放這
URL.applicationSupportDirectory
// 暫存、可拋棄的檔案放這
URL.cachesDirectory
FileManager
如果是在 iOS16 以前,或者是在 macOS 上的話,我們會透過 FileManager 來取得 URL。
你會需要選擇你要找的「資料夾(Directory)」和「區域(Domain)」。
💡 iOS 上的選擇建議
- 資料夾:
- documentDirectory:使用者的文件夾,主要會使用這個儲存資料,是使用者看得到的。
- applicationSupportDirectory:用來存一些不該被使用者看到的檔案。
- cachesDirectory:暫存資料。不會被備份。
- iOS 上,區域只能有「userDomainMask」可用。
有了這兩個資訊,就可以透過下面兩種方法取得 URL。
// 1. 回傳一個符合要求的 URL Array,可能會是空 Array。通常會用這個選第一個。
FileManager.default.urls(for: .applicationSupportDirectory,
in: .userDomainMask).first
// 2. 基本上和上面的一樣,會直接回傳找到的第一個 URL,但是找不到任何 URL 時會報錯。
try FileManager.default.url(for: .applicationSupportDirectory,
in: .userDomainMask,
appropriateFor: .none,
create: false)
# 結合成的完整 URL
有了基本的資料夾 URL 之後,我們就能透過 URL 的方法來組合出完整的 URL。這裡一樣可以分成 iOS16 的新方法,和之前的方法。
* iOS16 推出了 .append()
,將會取代舊有的 .appendingPathComponent()
方法。用法上大同小異,故以下僅介紹新方法。
append()
- .append(path: String)
- .append(component: String)
- .appendappend(components: String...)
💡 參數 path 和 component 的差別只有 component 會先進行 URL 編碼。
⚠️ 注意 .append() 是 mutating 方法,不是回傳新的 URL。
// 以下三種寫法都是同樣到「文件資料夾/newFolder/file.txt」。
var dir = URL.documentsDirectory
dir.append(path: "newFolder/file.txt")
var dir2 = URL.documentsDirectory
dir2.append(component: "newFolder/file.txt")
var dir3 = URL.documentsDirectory
dir3.append(components: "newFolder", "file.txt")
DirectoryHint
append 中有個可選參數 DirectoryHint,這個 enum 用來明確表示 URL 是否是資料夾。
- 預設值為「inferFromPath」,也就是我們需要自己管理好 URL 的結尾。
public enum DirectoryHint {
case isDirectory // 是資料夾
case isNotDirectory // 不是資料夾
case checkFileSystem // 應檢查檔案系統來確認是不是資料夾
case inferFromPath // 檢查 URL 最後是否以 / 結束來判斷是不是資料夾
}
appendingPathExtension()
有時候我們只是想加上副檔名,就可以使用這個方法。不會變成繼續接下一層「/」。
let dir = URL.documentsDirectory.appendingPathExtension("txt")
print(dir)
// ~/Documents.txt/
# 檢查檔案、資料夾是否已存在
在直接使用 URL 之前,我們通常會先檢查一下檔名是否已被使用、資料夾是否已經建立好。
FileManager.default.fileExists(atPath: dir.path())
# 新增資料夾
try FileManager.default.createDirectory(
at: dir,
withIntermediateDirectories: false
)
// 這個參數如果設為 true,會自動把中間不存在的資料夾也一起新增。
你可能會想先確認資料夾是否存在,如果不存在就新增資料夾。但是你沒辦法保證在「確認~新增」這段期間沒有其他地方同時在這個位置新增一樣的資料夾。
所以直接用 do catch 處理會是更安全的做法。
do {
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: false)
} catch CocoaError.fileWriteFileExists {
// 資料夾已存在
} catch {
// 處理其他錯誤
}
# 結語
經過「取得資料夾 URL」=> 「設定完整路徑」=>「確認資料夾與檔案是否存在」,這幾個步驟之後你就可以安全地使用這個 URL 了。
把這些步驟整理進你自己的 Manager,或是加進 FileManager 的 extension 中會讓你的程式碼更好用、整潔。