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 – useurls(for:in:)
only. - The
appropriateFor
argument is completely irrelevant to everySearchPathDirectory
exceptitemReplacementDirectory
andtrashDirectory
. - 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 forappropriateFor
when targetingitemReplacementDirectory
, yet it will allownil
when targetingtrashDirectory
, but only foruserDomainMask
andlocalDomainMask
. I cannot think of any explanation for this inconsistency.
If you ignore that inconsistency, theSearchPathDomainMask
is irrelevant totrashDirectory
(as it logically should be – bin folders are tied to volumes, not apps, users, or computers. - It always throws an exception for
itemReplacementDirectory
if theSearchPathDomainMask
is notuserDomainMask
. 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
andtrashDirectory
should not be part ofSearchPathDirectory
. 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 ofurls(for:in:)
. The other two should handle each of the special cases ofitemReplacementDirectory
andtrashDirectory
. 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 URL
s 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).
- 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. ↩︎