When should we use FileManager.default.urls as opposed to NSSearchPathForDirectoriesInDomains?

Viewed 1801

Currently, if I want to create a directory hierarchy in Document directory, I would perform the following


Using NSSearchPathForDirectoriesInDomains (Works fine)

let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory = paths[0]
let documentUrl1 = URL(string: documentsDirectory)!
//
// /Users/yccheok/Library/Developer/...
//
print("documentUrl1 -> \(documentUrl1)")

let dataPath = documentUrl1.appendingPathComponent("SubFolder1").appendingPathComponent("SubFolder2")
print("dataPath.absoluteString -> \(dataPath.absoluteString)")
if !FileManager.default.fileExists(atPath: dataPath.absoluteString) {
    do {
        try FileManager.default.createDirectory(atPath: dataPath.absoluteString, withIntermediateDirectories: true, attributes: nil)
        print("Folder creation done!")
    } catch {
        print(error.localizedDescription)
    }
}

But, if I use the following

Using FileManager.default.urls (Not working)

let documentUrl0 = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
//
// file:///Users/yccheok/Library/Developer/...
//
print("documentUrl0 -> \(documentUrl0)")

let dataPath = documentUrl0.appendingPathComponent("SubFolder1").appendingPathComponent("SubFolder2")
print("dataPath.absoluteString -> \(dataPath.absoluteString)")
if !FileManager.default.fileExists(atPath: dataPath.absoluteString) {
    do {
        try FileManager.default.createDirectory(atPath: dataPath.absoluteString, withIntermediateDirectories: true, attributes: nil)
        print("Folder creation done!")
    } catch {
        print(error.localizedDescription)
    }
}

The following error will be printed

You can’t save the file “SubFolder2” because the volume is read only.


I was wondering, under what use case, that FileManager.default.urls will be useful? Thanks.

1 Answers

URL(string and absoluteString are the wrong APIs to work with file system paths.

Your code accidentally works because URL(string applied to a path creates a path rather than a valid URL, and absoluteString applied to a path returns the path because there is no scheme (file://).

However you are strongly discouraged from doing that. A file system URL must be created with URL(fileURLWithPath and you can get the path with the path property.

You are encouraged to use the FileManager API because it provides a better error handling and it provides also the URL related API which is preferred over the string path API.

This is the correct FileManager way to create the directory if it doesn't exist

let fm = FileManager.default
do {
    let documentUrl = try fm.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
    //
    // file:///Users/yccheok/Library/Developer/...
    //
    print("documentUrl -> \(documentUrl)")
    
    let dataURL = documentUrl.appendingPathComponent("SubFolder1").appendingPathComponent("SubFolder2")
    print("dataPath.path -> \(dataURL.path)")
    if !fm.fileExists(atPath: dataURL.path) {
        try fm.createDirectory(at: dataURL, withIntermediateDirectories: true, attributes: nil)
        print("Folder creation done!")
    }
} catch { print(error) }
Related