How to get a unique temporary file path using Swift/Cocoa on OS X?
Cocoa does not seem to provide a function for this, only NSTemporaryDirectory() which returns the path of the temporary directory. Using the BSD mktemp function requires a mutable C-string as argument.
8 Answers
Apple has been trying to move away from path-as-string and into NSURL. Here's one way:
Swift 3:
let directory = NSTemporaryDirectory()
let fileName = NSUUID().uuidString
// This returns a URL? even though it is an NSURL class method
let fullURL = NSURL.fileURL(withPathComponents: [directory, fileName])
Swift 2:
let directory = NSTemporaryDirectory()
let fileName = NSUUID().UUIDString
let fullURL = NSURL.fileURLWithPathComponents([directory, fileName])
- 82,550
- 14
- 135
- 153
Here is a possible method to use mkstemp() from Swift 3 and later. URL methods
are used to convert between URL instances and C strings representing the file system path:
// The template string:
let template = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("file.XXXXXX") as NSURL
// Fill buffer with a C string representing the local file system path.
var buffer = [Int8](repeating: 0, count: Int(PATH_MAX))
template.getFileSystemRepresentation(&buffer, maxLength: buffer.count)
// Create unique file name (and open file):
let fd = mkstemp(&buffer)
if fd != -1 {
// Create URL from file system string:
let url = URL(fileURLWithFileSystemRepresentation: buffer, isDirectory: false, relativeTo: nil)
print(url.path)
} else {
print("Error: " + String(cString: strerror(errno)))
}
Older code for Swift 2:
// The template string:
let template = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent("file.XXXXXX")
// Fill buffer with a C string representing the local file system path.
var buffer = [Int8](count: Int(PATH_MAX), repeatedValue: 0)
template.getFileSystemRepresentation(&buffer, maxLength: buffer.count)
// Create unique file name (and open file):
let fd = mkstemp(&buffer)
if fd != -1 {
// Create URL from file system string:
let url = NSURL(fileURLWithFileSystemRepresentation: buffer, isDirectory: false, relativeToURL: nil)
print(url.path!)
} else {
print("Error: " + String(strerror(errno)))
}
- 510,973
- 84
- 1,183
- 1,314
-
2Nice. I think you should use `mkstemp` as the example instead of `mktemp` because it's generally safer. – jtbandes Sep 18 '15 at 17:53
-
1It is also needed to `close` the file descriptor `fd` from `mkstemp`? – tmlen Sep 18 '15 at 18:24
-
4@tmlen: Yes, otherwise you have a file descriptor leak. Alternatively, create a file handle with `let fh = NSFileHandle(fileDescriptor: fd, closeOnDealloc: true)` to transfer the ownership. The fd will be closed when the file handle is deallocated. – Martin R Sep 18 '15 at 18:33
Although NSTemporaryDirectory() does indeed return a temporary directory path for the current user, the documentation includes the following caveat:
See the
FileManagermethodurl(for:in:appropriateFor:create:)for the preferred means of finding the correct temporary directory.
Following that link, we are presented with the following:
You can use this method to create a new temporary directory. To do so, specify
FileManager.SearchPathDirectory.itemReplacementDirectoryfor thedirectoryparameter,userDomainMaskfor thedomainparameter, and a URL for theurlparameter which determines the volume of the returned URL.For example, the following code results in a new temporary directory with a path in the form of
/private/var/folders/d0/h37cw8ns3h1bfr_2gnwq2yyc0000gn/T/TemporaryItems/Untitled/:let desktop = URL(fileURLWithPath: "/Users/jappleseed/Desktop/") do { let temporaryDirectory = try FileManager.default.url( for: .itemReplacementDirectory, in: .userDomainMask, appropriateFor: desktop, create: true ) print(temporaryDirectory) } catch { // Handle the error. }
(Note the the create parameter is ignored when creating a temporary directory.)
So what exactly is the difference between these two approaches? Well, here's what I get when I call the two different methods from the Swift REPL:
1> import Foundation
2> NSTemporaryDirectory()
$R0: String = "/var/folders/n_/0_9q7d2d1ls5v9kx599y_tj00000gn/T/"
3> let desktop = URL(fileURLWithPath: "/Users/chris/Desktop/")
desktop: URL = "file:///Users/chris/Desktop/"
4> let temporaryDirectory = try FileManager.default.url(
5. for: .itemReplacementDirectory,
6. in: .userDomainMask,
7. appropriateFor: desktop,
8. create: true
9. )
temporaryDirectory: URL = "file:///var/folders/n_/0_9q7d2d1ls5v9kx599y_tj00000gn/T/TemporaryItems/(A%20Document%20Being%20Saved%20By%20repl_swift)/"
It appears that NSTemporaryDirectory() will always return the temporary directory path for the current user whereas FileManager's url(for:appropriateFor:create) will return a new temporary subdirectory each time it is called. For example, here are the directories returned by consecutive calls to url(for:in:appropriateFor:create:) from the Swift REPL:
file:///var/folders/n_/0_9q7d2d1ls5v9kx599y_tj00000gn/T/TemporaryItems/(A%20Document%20Being%20Saved%20By%20repl_swift)/file:///var/folders/n_/0_9q7d2d1ls5v9kx599y_tj00000gn/T/TemporaryItems/(A%20Document%20Being%20Saved%20By%20repl_swift%202)/file:///var/folders/n_/0_9q7d2d1ls5v9kx599y_tj00000gn/T/TemporaryItems/(A%20Document%20Being%20Saved%20By%20repl_swift%203)/
And here are the directories returned by consecutive calls to the same method from a Swift Playground:
file:///var/folders/n_/0_9q7d2d1ls5v9kx599y_tj00000gn/T/TemporaryItems/(A%20Document%20Being%20Saved%20By%20Xcode)/file:///var/folders/n_/0_9q7d2d1ls5v9kx599y_tj00000gn/T/TemporaryItems/(A%20Document%20Being%20Saved%20By%20Xcode%202)/file:///var/folders/n_/0_9q7d2d1ls5v9kx599y_tj00000gn/T/TemporaryItems/(A%20Document%20Being%20Saved%20By%20Xcode%203)/
The NSHipster article on temporary files seems to suggest that the FileManager method url(for:in:appropriateFor:create:) is intended to be used when staging a file to be moved to a more permanent location (such as the user's desktop in the example above), but I don't see why it couldn't also be used to simply get a unique subdirectory that will automatically be removed when you're done with it and where you shouldn't have to worry about files getting accidentally clobbered by other processes writing to the same temporary directory.
- 5,348
- 3
- 35
- 42
-
1Thanks for the analysis. Answers like this make SO *the* place to get answers. – mojuba Jan 10 '21 at 16:51
-
A Swift 3 one-liner inspired by the UUID based Swift 2 answer:
let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString)
FileManager extension in Swift to get a temporary file URL. You can pass your own file name and extension, if needed.
public extension FileManager {
func temporaryFileURL(fileName: String = UUID().uuidString) -> URL? {
return URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent(fileName)
}
}
Usage:
let tempURL = FileManager.default.temporaryFileURL()
let tempJPG = FileManager.default.temporaryFileURL(fileName: "temp.jpg")
- 619
- 8
- 7
Use a GUID (Globally Unique Identifier):
let directory :NSString = "directory"
let randomName = NSProcessInfo().globallyUniqueString
let path = directory.stringByAppendingPathComponent(randomName)
directory/3B635E49-813A-4324-B4B8-56279B42BEAB-36687-0002D962615DAE5F
- 110,296
- 20
- 185
- 221
I like the idea of this article: NSTemporaryDirectory - NSHipster
This uses the NSTemporaryDirectory() for the temporary folder and ProcessInfo.processInfo.globallyUniqueString to generate a unique string.
Swift 4:
func uniqueTempFolderURL() -> URL
{
let folderName = ProcessInfo.processInfo.globallyUniqueString
return URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent(folderName)
}
- 2,372
- 2
- 21
- 36
Swift3
I came here looking for something like boost::filesystem::unique_path()
So I made this extension to the URL class.
extension URL {
func appendingUniquePathComponent(pathExtension: String? = nil) -> URL {
var pathComponent = UUID().uuidString
if let pathExtension = pathExtension {
pathComponent += ".\(pathExtension)"
}
return appendingPathComponent(pathComponent)
}
}
Usage:
let url0 = URL(fileURLWithPath: "/tmp/some/dir")
let url1 = url0.appendingUniquePathComponent(pathExtension: "jpg")
print("url1: \(url1)")
// url1: file:///tmp/some/dir/936324FF-EEDB-410E-AD09-E24D5EB4A24F.jpg
- 47,456
- 24
- 159
- 149