Bad API example: FileManager’s url(for:in:appropriateFor:create:)

I find FileManager‘s url(for:in:appropriateFor:create:) to be very unintuitive. It seems to have multiple, largely-orthogonal functions. It can provide paths to common folders (albeit badly). It can create temporary folders. It can locate volume-specific bins (Trash folders).

It is an example of bad API design. Specifically, regarding cohesion: the principle that an API should have one purpose. A litmus test for this is whether all the method parameters are always applicable1.

It wasn’t until I wrote a test driver which explores its entire parameter space, that I was finally able to grok what the hell it’s doing and delineate its multiple modes of operation.

I’ve contrasted it with the results from its sibling urls(for:in:), to better understand what it’s doing (and expose some more of its flaws).

Test driver

You can run this in a Swift playground, or as CLI app, but to observe the behaviour inside an App Sandbox it’s easiest to create a new GUI app in Xcode and just dump this into the @main App struct’s init method.

let fm = FileManager.default

let searchPathDirectories = [("applicationDirectory", FileManager.SearchPathDirectory.applicationDirectory),
                             ("demoApplicationDirectory", FileManager.SearchPathDirectory.demoApplicationDirectory),
                             ("developerApplicationDirectory", FileManager.SearchPathDirectory.developerApplicationDirectory),
                             ("adminApplicationDirectory", FileManager.SearchPathDirectory.adminApplicationDirectory),
                             ("libraryDirectory", FileManager.SearchPathDirectory.libraryDirectory),
                             ("developerDirectory", FileManager.SearchPathDirectory.developerDirectory),
                             ("userDirectory", FileManager.SearchPathDirectory.userDirectory),
                             ("documentationDirectory", FileManager.SearchPathDirectory.documentationDirectory),
                             ("documentDirectory", FileManager.SearchPathDirectory.documentDirectory),
                             ("coreServiceDirectory", FileManager.SearchPathDirectory.coreServiceDirectory),
                             ("autosavedInformationDirectory", FileManager.SearchPathDirectory.autosavedInformationDirectory),
                             ("desktopDirectory", FileManager.SearchPathDirectory.desktopDirectory),
                             ("cachesDirectory", FileManager.SearchPathDirectory.cachesDirectory),
                             ("applicationSupportDirectory", FileManager.SearchPathDirectory.applicationSupportDirectory),
                             ("downloadsDirectory", FileManager.SearchPathDirectory.downloadsDirectory),
                             ("inputMethodsDirectory", FileManager.SearchPathDirectory.inputMethodsDirectory),
                             ("moviesDirectory", FileManager.SearchPathDirectory.moviesDirectory),
                             ("musicDirectory", FileManager.SearchPathDirectory.musicDirectory),
                             ("picturesDirectory", FileManager.SearchPathDirectory.picturesDirectory),
                             ("printerDescriptionDirectory", FileManager.SearchPathDirectory.printerDescriptionDirectory),
                             ("sharedPublicDirectory", FileManager.SearchPathDirectory.sharedPublicDirectory),
                             ("preferencePanesDirectory", FileManager.SearchPathDirectory.preferencePanesDirectory),
                             ("applicationScriptsDirectory", FileManager.SearchPathDirectory.applicationScriptsDirectory),
                             ("itemReplacementDirectory", FileManager.SearchPathDirectory.itemReplacementDirectory),
                             ("allApplicationsDirectory", FileManager.SearchPathDirectory.allApplicationsDirectory),
                             ("allLibrariesDirectory", FileManager.SearchPathDirectory.allLibrariesDirectory),
                             ("trashDirectory", FileManager.SearchPathDirectory.trashDirectory)]

let searchPathDomainMasks = [("userDomainMask", FileManager.SearchPathDomainMask.userDomainMask),
                             ("localDomainMask", FileManager.SearchPathDomainMask.localDomainMask),
                             ("systemDomainMask", FileManager.SearchPathDomainMask.systemDomainMask),
                             ("networkDomainMask", FileManager.SearchPathDomainMask.networkDomainMask),
                             /*("allDomainsMask", FileManager.SearchPathDomainMask.allDomainsMask)*/]

print("urls(for:in:):")

for (dirName, dir) in searchPathDirectories {
    print("\n\(dirName):")

    for (domainName, domain) in searchPathDomainMasks {
        let dirs = fm.urls(for: dir, in: domain)
            .map { $0.path(percentEncoded: false) }
            .joined(separator: "\n" + String(repeating: " ", count: 23))

        print("    \(domainName): \(String(repeating: " ", count: 17 - domainName.count))\(dirs)")
    }
}

print("\n\nurl(for:in:appropriateFor:create:):")

let paths = [nil,
             URL(filePath: "/"),
             FileManager.default.temporaryDirectory,
             URL(filePath: "/Volumes/Flash/")]
let pathDesc: (URL?) -> String = { $0?.path(percentEncoded: false) ?? "nil" }
let maxPathLength = paths.map { pathDesc($0).count }.max() ?? 0

for (dirName, dir) in searchPathDirectories {
    print("\n\(dirName):")

    for (domainName, domain) in searchPathDomainMasks {
        var results = [String: String]()

        for appropriateForPath in paths {
            let path: String

            do {
                let folderURL = try FileManager.default.url(for: dir,
                                                            in: domain,
                                                            appropriateFor: appropriateForPath,
                                                            create: false)
                path = pathDesc(folderURL)
            } catch {
                path = "ERROR (\(error))"
            }

            results[pathDesc(appropriateForPath)] = path
        }

        let uniquePaths = Set(results.values)

        if 1 == uniquePaths.count {
            print("    \(domainName): \(String(repeating: " ", count: 17 - domainName.count))\(uniquePaths.first!)")
        } else {
            print("    \(domainName):")

            for (appropriateForPath, path) in results.sorted(by: { $0.key < $1.key }) {
                print("        \(appropriateForPath): \(String(repeating: " ", count: maxPathLength - appropriateForPath.count))\(path)")
            }
        }
    }
}

And here’s the output:

When running inside an App Sandbox:
urls(for:in:):
applicationDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Applications/
localDomainMask:   /Applications/
systemDomainMask:  /System/Applications/
/System/Cryptexes/App/System/Applications/
networkDomainMask: /Network/Applications/
demoApplicationDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Applications/Demos/
localDomainMask:   /Applications/Demos/
systemDomainMask:  /Applications/Demos/
networkDomainMask: /Network/Applications/Demos/
developerApplicationDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Developer/Applications/
localDomainMask:   /Developer/Applications/
systemDomainMask:  /Developer/Applications/
networkDomainMask: /Network/Developer/Applications/
adminApplicationDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Applications/Utilities/
localDomainMask:   /Applications/Utilities/
systemDomainMask:  /System/Applications/Utilities/
/System/Cryptexes/App/System/Applications/Utilities/
networkDomainMask: /Network/Applications/Utilities/
libraryDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/
localDomainMask:   /Library/
systemDomainMask:  /System/Library/
/System/Cryptexes/App/System/Library/
networkDomainMask: /Network/Library/
developerDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Developer/
localDomainMask:   /Developer/
systemDomainMask:  /Developer/
networkDomainMask: /Network/Developer/
userDirectory:
userDomainMask:    
localDomainMask:   /Users/
systemDomainMask:  
networkDomainMask: /Network/Users/
documentationDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/Documentation/
localDomainMask:   /Library/Documentation/
systemDomainMask:  /System/Library/Documentation/
/System/Cryptexes/App/System/Library/Documentation/
networkDomainMask: /Network/Library/Documentation/
documentDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Documents/
localDomainMask:   
systemDomainMask:  
networkDomainMask: 
coreServiceDirectory:
userDomainMask:    
localDomainMask:   
systemDomainMask:  /System/Library/CoreServices/
/System/Cryptexes/App/System/Library/CoreServices/
networkDomainMask: 
autosavedInformationDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/Autosave Information/
localDomainMask:   
systemDomainMask:  
networkDomainMask: 
desktopDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Desktop/
localDomainMask:   
systemDomainMask:  
networkDomainMask: 
cachesDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/Caches/
localDomainMask:   /Library/Caches/
systemDomainMask:  /System/Library/Caches/
networkDomainMask: 
applicationSupportDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/Application Support/
localDomainMask:   /Library/Application Support/
systemDomainMask:  /Library/Application Support/
networkDomainMask: /Network/Library/Application Support/
downloadsDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Downloads/
localDomainMask:   
systemDomainMask:  
networkDomainMask: 
inputMethodsDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/Input Methods/
localDomainMask:   /Library/Input Methods/
systemDomainMask:  /System/Library/Input Methods/
/System/Cryptexes/App/System/Library/Input Methods/
networkDomainMask: /Network/Library/Input Methods/
moviesDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Movies/
localDomainMask:   
systemDomainMask:  
networkDomainMask: 
musicDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Music/
localDomainMask:   
systemDomainMask:  
networkDomainMask: 
picturesDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Pictures/
localDomainMask:   
systemDomainMask:  
networkDomainMask: 
printerDescriptionDirectory:
userDomainMask:    
localDomainMask:   
systemDomainMask:  /System/Library/Printers/PPDs/
/System/Cryptexes/App/System/Library/Printers/PPDs/
networkDomainMask: 
sharedPublicDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Public/
localDomainMask:   
systemDomainMask:  
networkDomainMask: 
preferencePanesDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/PreferencePanes/
localDomainMask:   /Library/PreferencePanes/
systemDomainMask:  /System/Library/PreferencePanes/
/System/Cryptexes/App/System/Library/PreferencePanes/
networkDomainMask: 
applicationScriptsDirectory:
userDomainMask:    /Users/SadPanda/Library/Application Scripts/com.SadPanda.MyApp/
localDomainMask:   
systemDomainMask:  
networkDomainMask: 
itemReplacementDirectory:
userDomainMask:    
localDomainMask:   
systemDomainMask:  
networkDomainMask: 
allApplicationsDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Applications/
/Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Applications/Utilities/
/Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Developer/Applications/
/Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Applications/Demos/
localDomainMask:   /Applications/
/Applications/Utilities/
/Developer/Applications/
/Applications/Demos/
systemDomainMask:  /System/Applications/
/System/Applications/Utilities/
/System/Developer/Applications/
/System/Applications/Demos/
/System/Cryptexes/App/System/Applications/
/System/Cryptexes/App/System/Applications/Utilities/
/System/Cryptexes/App/System/Developer/Applications/
/System/Cryptexes/App/System/Applications/Demos/
networkDomainMask: /Network/Applications/
/Network/Applications/Utilities/
/Network/Developer/Applications/
/Network/Applications/Demos/
allLibrariesDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/
/Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Developer/
localDomainMask:   /Library/
/Developer/
systemDomainMask:  /System/Library/
/Developer/
/System/Cryptexes/App/System/Library/
/System/Cryptexes/App/System/Developer/
networkDomainMask: /Network/Library/
/Network/Developer/
trashDirectory:
userDomainMask:    /Users/SadPanda/.Trash/
localDomainMask:   /Users/SadPanda/.Trash/
systemDomainMask:  
networkDomainMask: 
url(for:in:appropriateFor:create:):
applicationDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Applications/
localDomainMask:   /Applications/
systemDomainMask:  /System/Applications/
networkDomainMask: /Network/Applications/
demoApplicationDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Applications/Demos/
localDomainMask:   /Applications/Demos/
systemDomainMask:  /Applications/Demos/
networkDomainMask: /Network/Applications/Demos/
developerApplicationDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Developer/Applications/
localDomainMask:   /Developer/Applications/
systemDomainMask:  /Developer/Applications/
networkDomainMask: /Network/Developer/Applications/
adminApplicationDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Applications/Utilities/
localDomainMask:   /Applications/Utilities/
systemDomainMask:  /System/Applications/Utilities/
networkDomainMask: /Network/Applications/Utilities/
libraryDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/
localDomainMask:   /Library/
systemDomainMask:  /System/Library/
networkDomainMask: /Network/Library/
developerDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Developer/
localDomainMask:   /Developer/
systemDomainMask:  /Developer/
networkDomainMask: /Network/Developer/
userDirectory:
userDomainMask:    ERROR (nilError)
localDomainMask:   /Users/
systemDomainMask:  ERROR (nilError)
networkDomainMask: /Network/Users/
documentationDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/Documentation/
localDomainMask:   /Library/Documentation/
systemDomainMask:  /System/Library/Documentation/
networkDomainMask: /Network/Library/Documentation/
documentDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Documents/
localDomainMask:   ERROR (nilError)
systemDomainMask:  ERROR (nilError)
networkDomainMask: ERROR (nilError)
coreServiceDirectory:
userDomainMask:    ERROR (nilError)
localDomainMask:   ERROR (nilError)
systemDomainMask:  /System/Library/CoreServices/
networkDomainMask: ERROR (nilError)
autosavedInformationDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/Autosave Information/
localDomainMask:   ERROR (nilError)
systemDomainMask:  ERROR (nilError)
networkDomainMask: ERROR (nilError)
desktopDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Desktop/
localDomainMask:   ERROR (nilError)
systemDomainMask:  ERROR (nilError)
networkDomainMask: ERROR (nilError)
cachesDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/Caches/
localDomainMask:   /Library/Caches/
systemDomainMask:  /System/Library/Caches/
networkDomainMask: ERROR (nilError)
applicationSupportDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/Application Support/
localDomainMask:   /Library/Application Support/
systemDomainMask:  /Library/Application Support/
networkDomainMask: /Network/Library/Application Support/
downloadsDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Downloads/
localDomainMask:   ERROR (nilError)
systemDomainMask:  ERROR (nilError)
networkDomainMask: ERROR (nilError)
inputMethodsDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/Input Methods/
localDomainMask:   /Library/Input Methods/
systemDomainMask:  /System/Library/Input Methods/
networkDomainMask: /Network/Library/Input Methods/
moviesDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Movies/
localDomainMask:   ERROR (nilError)
systemDomainMask:  ERROR (nilError)
networkDomainMask: ERROR (nilError)
musicDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Music/
localDomainMask:   ERROR (nilError)
systemDomainMask:  ERROR (nilError)
networkDomainMask: ERROR (nilError)
picturesDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Pictures/
localDomainMask:   ERROR (nilError)
systemDomainMask:  ERROR (nilError)
networkDomainMask: ERROR (nilError)
printerDescriptionDirectory:
userDomainMask:    ERROR (nilError)
localDomainMask:   ERROR (nilError)
systemDomainMask:  /System/Library/Printers/PPDs/
networkDomainMask: ERROR (nilError)
sharedPublicDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Public/
localDomainMask:   ERROR (nilError)
systemDomainMask:  ERROR (nilError)
networkDomainMask: ERROR (nilError)
preferencePanesDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/PreferencePanes/
localDomainMask:   /Library/PreferencePanes/
systemDomainMask:  /System/Library/PreferencePanes/
networkDomainMask: ERROR (nilError)
applicationScriptsDirectory:
userDomainMask:    /Users/SadPanda/Library/Application Scripts/com.SadPanda.MyApp/
localDomainMask:   ERROR (nilError)
systemDomainMask:  ERROR (nilError)
networkDomainMask: ERROR (nilError)
itemReplacementDirectory:
userDomainMask:
/:                                                               /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/tmp/TemporaryItems/NSIRD_MyApp_oflT6r/
/Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/tmp/: /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/tmp/TemporaryItems/NSIRD_MyApp_xkFfky/
/Volumes/Other/:                                                 /Volumes/Other/.TemporaryItems/folders.501/TemporaryItems/NSIRD_MyApp_bLj7gn/
nil:                                                             ERROR (nilError)
localDomainMask:   ERROR (nilError)
systemDomainMask:  ERROR (nilError)
networkDomainMask: ERROR (nilError)
allApplicationsDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Applications/
localDomainMask:   /Applications/
systemDomainMask:  /System/Applications/Demos/
networkDomainMask: /Network/Applications/
allLibrariesDirectory:
userDomainMask:    /Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/Library/
localDomainMask:   /Library/
systemDomainMask:  /Developer/
networkDomainMask: /Network/Library/
trashDirectory:
userDomainMask:
/:                                                               /Users/SadPanda/.Trash/
/Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/tmp/: /Users/SadPanda/.Trash/
/Volumes/Other/:                                                 /Volumes/Other/.Trashes/501/
nil:                                                             /Users/SadPanda/.Trash/
localDomainMask:
/:                                                               /Users/SadPanda/.Trash/
/Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/tmp/: /Users/SadPanda/.Trash/
/Volumes/Other/:                                                 /Volumes/Other/.Trashes/501/
nil:                                                             /Users/SadPanda/.Trash/
systemDomainMask:
/:                                                               /Users/SadPanda/.Trash/
/Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/tmp/: /Users/SadPanda/.Trash/
/Volumes/Other/:                                                 /Volumes/Other/.Trashes/501/
nil:                                                             ERROR (nilError)
networkDomainMask:
/:                                                               /Users/SadPanda/.Trash/
/Users/SadPanda/Library/Containers/com.SadPanda.MyApp/Data/tmp/: /Users/SadPanda/.Trash/
/Volumes/Other/:                                                 /Volumes/Other/.Trashes/501/
nil:                                                             ERROR (nilError)

The output outside of an App Sandbox is pretty similar, just different paths in some cases as you’d expect.

When running without App Sandboxing
urls(for:in:):
applicationDirectory:
userDomainMask:    /Users/SadPanda/Applications/
localDomainMask:   /Applications/
systemDomainMask:  /System/Applications/
/System/Cryptexes/App/System/Applications/
networkDomainMask: /Network/Applications/
demoApplicationDirectory:
userDomainMask:    /Users/SadPanda/Applications/Demos/
localDomainMask:   /Applications/Demos/
systemDomainMask:  /Applications/Demos/
networkDomainMask: /Network/Applications/Demos/
developerApplicationDirectory:
userDomainMask:    /Users/SadPanda/Developer/Applications/
localDomainMask:   /Developer/Applications/
systemDomainMask:  /Developer/Applications/
networkDomainMask: /Network/Developer/Applications/
adminApplicationDirectory:
userDomainMask:    /Users/SadPanda/Applications/Utilities/
localDomainMask:   /Applications/Utilities/
systemDomainMask:  /System/Applications/Utilities/
/System/Cryptexes/App/System/Applications/Utilities/
networkDomainMask: /Network/Applications/Utilities/
libraryDirectory:
userDomainMask:    /Users/SadPanda/Library/
localDomainMask:   /Library/
systemDomainMask:  /System/Library/
/System/Cryptexes/App/System/Library/
networkDomainMask: /Network/Library/
developerDirectory:
userDomainMask:    /Users/SadPanda/Developer/
localDomainMask:   /Developer/
systemDomainMask:  /Developer/
networkDomainMask: /Network/Developer/
userDirectory:
userDomainMask:    
localDomainMask:   /Users/
systemDomainMask:  
networkDomainMask: /Network/Users/
documentationDirectory:
userDomainMask:    /Users/SadPanda/Library/Documentation/
localDomainMask:   /Library/Documentation/
systemDomainMask:  /System/Library/Documentation/
/System/Cryptexes/App/System/Library/Documentation/
networkDomainMask: /Network/Library/Documentation/
documentDirectory:
userDomainMask:    /Users/SadPanda/Documents/
localDomainMask:   
systemDomainMask:  
networkDomainMask: 
coreServiceDirectory:
userDomainMask:    
localDomainMask:   
systemDomainMask:  /System/Library/CoreServices/
/System/Cryptexes/App/System/Library/CoreServices/
networkDomainMask: 
autosavedInformationDirectory:
userDomainMask:    /Users/SadPanda/Library/Autosave Information/
localDomainMask:   
systemDomainMask:  
networkDomainMask: 
desktopDirectory:
userDomainMask:    /Users/SadPanda/Desktop/
localDomainMask:   
systemDomainMask:  
networkDomainMask: 
cachesDirectory:
userDomainMask:    /Users/SadPanda/Library/Caches/
localDomainMask:   /Library/Caches/
systemDomainMask:  /System/Library/Caches/
networkDomainMask: 
applicationSupportDirectory:
userDomainMask:    /Users/SadPanda/Library/Application Support/
localDomainMask:   /Library/Application Support/
systemDomainMask:  /Library/Application Support/
networkDomainMask: /Network/Library/Application Support/
downloadsDirectory:
userDomainMask:    /Users/SadPanda/Downloads/
localDomainMask:   
systemDomainMask:  
networkDomainMask: 
inputMethodsDirectory:
userDomainMask:    /Users/SadPanda/Library/Input Methods/
localDomainMask:   /Library/Input Methods/
systemDomainMask:  /System/Library/Input Methods/
/System/Cryptexes/App/System/Library/Input Methods/
networkDomainMask: /Network/Library/Input Methods/
moviesDirectory:
userDomainMask:    /Users/SadPanda/Movies/
localDomainMask:   
systemDomainMask:  
networkDomainMask: 
musicDirectory:
userDomainMask:    /Users/SadPanda/Music/
localDomainMask:   
systemDomainMask:  
networkDomainMask: 
picturesDirectory:
userDomainMask:    /Users/SadPanda/Pictures/
localDomainMask:   
systemDomainMask:  
networkDomainMask: 
printerDescriptionDirectory:
userDomainMask:    
localDomainMask:   
systemDomainMask:  /System/Library/Printers/PPDs/
/System/Cryptexes/App/System/Library/Printers/PPDs/
networkDomainMask: 
sharedPublicDirectory:
userDomainMask:    /Users/SadPanda/Public/
localDomainMask:   
systemDomainMask:  
networkDomainMask: 
preferencePanesDirectory:
userDomainMask:    /Users/SadPanda/Library/PreferencePanes/
localDomainMask:   /Library/PreferencePanes/
systemDomainMask:  /System/Library/PreferencePanes/
/System/Cryptexes/App/System/Library/PreferencePanes/
networkDomainMask: 
applicationScriptsDirectory:
userDomainMask:    /Users/SadPanda/Library/Application Scripts/com.SadPanda.MyApp/
localDomainMask:   
systemDomainMask:  
networkDomainMask: 
itemReplacementDirectory:
userDomainMask:    
localDomainMask:   
systemDomainMask:  
networkDomainMask: 
allApplicationsDirectory:
userDomainMask:    /Users/SadPanda/Applications/
/Users/SadPanda/Applications/Utilities/
/Users/SadPanda/Developer/Applications/
/Users/SadPanda/Applications/Demos/
localDomainMask:   /Applications/
/Applications/Utilities/
/Developer/Applications/
/Applications/Demos/
systemDomainMask:  /System/Applications/
/System/Applications/Utilities/
/System/Developer/Applications/
/System/Applications/Demos/
/System/Cryptexes/App/System/Applications/
/System/Cryptexes/App/System/Applications/Utilities/
/System/Cryptexes/App/System/Developer/Applications/
/System/Cryptexes/App/System/Applications/Demos/
networkDomainMask: /Network/Applications/
/Network/Applications/Utilities/
/Network/Developer/Applications/
/Network/Applications/Demos/
allLibrariesDirectory:
userDomainMask:    /Users/SadPanda/Library/
/Users/SadPanda/Developer/
localDomainMask:   /Library/
/Developer/
systemDomainMask:  /System/Library/
/Developer/
/System/Cryptexes/App/System/Library/
/System/Cryptexes/App/System/Developer/
networkDomainMask: /Network/Library/
/Network/Developer/
trashDirectory:
userDomainMask:    /Users/SadPanda/.Trash/
localDomainMask:   /Users/SadPanda/.Trash/
systemDomainMask:  
networkDomainMask: 
url(for:in:appropriateFor:create:):
applicationDirectory:
userDomainMask:    /Users/SadPanda/Applications/
localDomainMask:   /Applications/
systemDomainMask:  /System/Applications/
networkDomainMask: /Network/Applications/
demoApplicationDirectory:
userDomainMask:    /Users/SadPanda/Applications/Demos/
localDomainMask:   /Applications/Demos/
systemDomainMask:  /Applications/Demos/
networkDomainMask: /Network/Applications/Demos/
developerApplicationDirectory:
userDomainMask:    /Users/SadPanda/Developer/Applications/
localDomainMask:   /Developer/Applications/
systemDomainMask:  /Developer/Applications/
networkDomainMask: /Network/Developer/Applications/
adminApplicationDirectory:
userDomainMask:    /Users/SadPanda/Applications/Utilities/
localDomainMask:   /Applications/Utilities/
systemDomainMask:  /System/Applications/Utilities/
networkDomainMask: /Network/Applications/Utilities/
libraryDirectory:
userDomainMask:    /Users/SadPanda/Library/
localDomainMask:   /Library/
systemDomainMask:  /System/Library/
networkDomainMask: /Network/Library/
developerDirectory:
userDomainMask:    /Users/SadPanda/Developer/
localDomainMask:   /Developer/
systemDomainMask:  /Developer/
networkDomainMask: /Network/Developer/
userDirectory:
userDomainMask:    ERROR (nilError)
localDomainMask:   /Users/
systemDomainMask:  ERROR (nilError)
networkDomainMask: /Network/Users/
documentationDirectory:
userDomainMask:    /Users/SadPanda/Library/Documentation/
localDomainMask:   /Library/Documentation/
systemDomainMask:  /System/Library/Documentation/
networkDomainMask: /Network/Library/Documentation/
documentDirectory:
userDomainMask:    /Users/SadPanda/Documents/
localDomainMask:   ERROR (nilError)
systemDomainMask:  ERROR (nilError)
networkDomainMask: ERROR (nilError)
coreServiceDirectory:
userDomainMask:    ERROR (nilError)
localDomainMask:   ERROR (nilError)
systemDomainMask:  /System/Library/CoreServices/
networkDomainMask: ERROR (nilError)
autosavedInformationDirectory:
userDomainMask:    /Users/SadPanda/Library/Autosave Information/
localDomainMask:   ERROR (nilError)
systemDomainMask:  ERROR (nilError)
networkDomainMask: ERROR (nilError)
desktopDirectory:
userDomainMask:    /Users/SadPanda/Desktop/
localDomainMask:   ERROR (nilError)
systemDomainMask:  ERROR (nilError)
networkDomainMask: ERROR (nilError)
cachesDirectory:
userDomainMask:    /Users/SadPanda/Library/Caches/
localDomainMask:   /Library/Caches/
systemDomainMask:  /System/Library/Caches/
networkDomainMask: ERROR (nilError)
applicationSupportDirectory:
userDomainMask:    /Users/SadPanda/Library/Application Support/
localDomainMask:   /Library/Application Support/
systemDomainMask:  /Library/Application Support/
networkDomainMask: /Network/Library/Application Support/
downloadsDirectory:
userDomainMask:    /Users/SadPanda/Downloads/
localDomainMask:   ERROR (nilError)
systemDomainMask:  ERROR (nilError)
networkDomainMask: ERROR (nilError)
inputMethodsDirectory:
userDomainMask:    /Users/SadPanda/Library/Input Methods/
localDomainMask:   /Library/Input Methods/
systemDomainMask:  /System/Library/Input Methods/
networkDomainMask: /Network/Library/Input Methods/
moviesDirectory:
userDomainMask:    /Users/SadPanda/Movies/
localDomainMask:   ERROR (nilError)
systemDomainMask:  ERROR (nilError)
networkDomainMask: ERROR (nilError)
musicDirectory:
userDomainMask:    /Users/SadPanda/Music/
localDomainMask:   ERROR (nilError)
systemDomainMask:  ERROR (nilError)
networkDomainMask: ERROR (nilError)
picturesDirectory:
userDomainMask:    /Users/SadPanda/Pictures/
localDomainMask:   ERROR (nilError)
systemDomainMask:  ERROR (nilError)
networkDomainMask: ERROR (nilError)
printerDescriptionDirectory:
userDomainMask:    ERROR (nilError)
localDomainMask:   ERROR (nilError)
systemDomainMask:  /System/Library/Printers/PPDs/
networkDomainMask: ERROR (nilError)
sharedPublicDirectory:
userDomainMask:    /Users/SadPanda/Public/
localDomainMask:   ERROR (nilError)
systemDomainMask:  ERROR (nilError)
networkDomainMask: ERROR (nilError)
preferencePanesDirectory:
userDomainMask:    /Users/SadPanda/Library/PreferencePanes/
localDomainMask:   /Library/PreferencePanes/
systemDomainMask:  /System/Library/PreferencePanes/
networkDomainMask: ERROR (nilError)
applicationScriptsDirectory:
userDomainMask:    /Users/SadPanda/Library/Application Scripts/com.SadPanda.MyApp/
localDomainMask:   ERROR (nilError)
systemDomainMask:  ERROR (nilError)
networkDomainMask: ERROR (nilError)
itemReplacementDirectory:
userDomainMask:
/:                                                 /var/folders/v3/8anb56f64adf3_35gj346jg13000xa/T/TemporaryItems/NSIRD_MyApp_6D4CLt/
/Volumes/Other/:                                   /Volumes/Other/.TemporaryItems/folders.501/TemporaryItems/NSIRD_MyApp_fdKnpT/
/var/folders/v3/8anb56f64adf3_35gj346jg13000xa/T/: /var/folders/v3/8anb56f64adf3_35gj346jg13000xa/T/TemporaryItems/NSIRD_MyApp_bcHf11/
nil:                                               ERROR (nilError)
localDomainMask:   ERROR (nilError)
systemDomainMask:  ERROR (nilError)
networkDomainMask: ERROR (nilError)
allApplicationsDirectory:
userDomainMask:    /Users/SadPanda/Applications/
localDomainMask:   /Applications/
systemDomainMask:  /System/Applications/Demos/
networkDomainMask: /Network/Applications/
allLibrariesDirectory:
userDomainMask:    /Users/SadPanda/Library/
localDomainMask:   /Library/
systemDomainMask:  /Developer/
networkDomainMask: /Network/Library/
trashDirectory:
userDomainMask:
/:                                                 /Users/SadPanda/.Trash/
/Volumes/Other/:                                   /Volumes/Other/.Trashes/501/
/var/folders/v3/8anb56f64adf3_35gj346jg13000xa/T/: /Users/SadPanda/.Trash/
nil:                                               /Users/SadPanda/.Trash/
localDomainMask:
/:                                                 /Users/SadPanda/.Trash/
/Volumes/Other/:                                   /Volumes/Other/.Trashes/501/
/var/folders/v3/8anb56f64adf3_35gj346jg13000xa/T/: /Users/SadPanda/.Trash/
nil:                                               /Users/SadPanda/.Trash/
systemDomainMask:
/:                                                 /Users/SadPanda/.Trash/
/Volumes/Other/:                                   /Volumes/Other/.Trashes/501/
/var/folders/v3/8anb56f64adf3_35gj346jg13000xa/T/: /Users/SadPanda/.Trash/
nil:                                               ERROR (nilError)
networkDomainMask:
/:                                                 /Users/SadPanda/.Trash/
/Volumes/Other/:                                   /Volumes/Other/.Trashes/501/
/var/folders/v3/8anb56f64adf3_35gj346jg13000xa/T/: /Users/SadPanda/.Trash/
nil:                                               ERROR (nilError)

Note how urls(for:in:) works for every SearchPathDirectory except itemReplacementDirectory.

Also, something is broken regarding applicationScriptsDirectory. When used with urls(for:in:) you get this output to stdout:

cannot open file at line 49259 of [1b37c146ee]
os_unix.c:49259: (0) open(/private/var/db/DetachedSignatures) - Undefined error: 0

And if you use it with url(for:in:appropriateFor:create:) you get eight lines of this output to stderr:

This method should not be called on the main thread as it may lead to UI unresponsiveness.

I haven’t delved into the implementation to figure out what’s going on – apparently that particular SearchPathDirectory has some kind of special code path all of its own, which is doing something it seemingly should not be doing.

Some observations about urls(for:in:appropriateFor:create:):

  • It returns a single URL, even though there are often multiple folders for a given search path. Note how it never returns a path to a Cryptex folder, for example.

    If you’re looking for search paths – the set of folders to search for a resource – use urls(for:in:) only.
  • The appropriateFor argument is completely irrelevant to every SearchPathDirectory except itemReplacementDirectory and trashDirectory.
  • It will only ever return a path inside the App Sandbox for userDomainMask (if it doesn’t fail by throwing an exception).
  • It won’t allow nil as an argument for appropriateFor when targeting itemReplacementDirectory, yet it will allow nil when targeting trashDirectory, but only for userDomainMask and localDomainMask. I cannot think of any explanation for this inconsistency.

    If you ignore that inconsistency, the SearchPathDomainMask is irrelevant to trashDirectory (as it logically should be – bin folders are tied to volumes, not apps, users, or computers.
  • It always throws an exception for itemReplacementDirectory if the SearchPathDomainMask is not userDomainMask. This might actually be explicable, even if a bit unintuitive and misguided – it seems to be presuming that, because you’re running inside an App Sandbox, you cannot modify any files except the current user’s. I’m not sure that’s not accurate – there’s the possibility of special entitlements and exclusions – although nor do I know that it’s not.
  • Whenever it fails, it throws a nilError exception which is largely useless. Even the name doesn’t provide any real insight into what its problem is.

    It’s unsurprising to me that it does such a poor job, given the rest of the API – good error messages are a hallmark of good API design, or put conversely, happy path programming produces bad code.

I did not explore use of the create argument (I always left it as false). I didn’t need to in order to know it has its own problems, too – the documentation states that it also has inconsistent behaviour, in that it is completely ignored for itemReplacementDirectory (and only that SearchPathDirectory).

The root of all these problems is the API’s bad design:

  • itemReplacementDirectory and trashDirectory should not be part of SearchPathDirectory. They do not behave like any of the other cases; they do not have broadly correct generic values, requiring instead some context regarding the items being replaced or trashed.
  • url(for:in:appropriateFor:create) should be three independent methods – one of which already exists, in the form of urls(for:in:). The other two should handle each of the special cases of itemReplacementDirectory and trashDirectory. i.e.:
// Existing method
func urls(for directory: FileManager.SearchPathDirectory,
          in domainMask: FileManager.SearchPathDomainMask) -> [URL]

// New methods
func replacementItemFolder(appropriateFor url: URL) throws(InvalidURLError) -> URL

func trashFolder(appropriateFor url: URL) throws(InvalidURLError) -> URL

So much clearer and easier to use than what we have now.

The two specialised methods still need to throw, because they take URLs which could be invalid, but that’s now the only reason they might throw (I’ve used a hypothetical InvalidURLError type to express that formally).

They could arguably be combined into one method (taking an enum that delineates the two cases), but I see little practical value in that – it’s simpler and easier to read if they’re simply distinct methods, even though their shape is similar. I can’t imagine any reasonable scenario where you only decide at runtime if you’re going to replace something or delete it.

☝️ I’ve removed the create parameter entirely, as I think it presumes too much (e.g. what if you want to create a file with the replacement item URL, not a folder?), though arguably it could be added back in (though if it were, it should actually be obeyed).

  1. Note that I wrote applicable, not necessarily used. The distinction is important. e.g. it makes sense to supply the request headers for a HTTP request to an API which makes such requests, even if the request might be served from a local cache in which case those headers aren’t actually used, in that instance.

    Another way to frame this is that it’s a design flaw to have parameters whose utility depends only on the value of other parameters.

    If you can know at compile time that some parameters are unnecessary, then you shouldn’t be forced to provide them. ↩︎

Leave a Comment