32

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.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
tmlen
  • 8,533
  • 5
  • 31
  • 84

9 Answers9

39

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])
Code Different
  • 90,614
  • 16
  • 144
  • 163
30

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)))
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • 2
    Nice. I think you should use `mkstemp` as the example instead of `mktemp` because it's generally safer. – jtbandes Sep 18 '15 at 17:53
  • 1
    It 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
19

Although NSTemporaryDirectory() does indeed return a temporary directory path for the current user, the documentation includes the following caveat:

See the FileManager method url(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.itemReplacementDirectory for the directory parameter, userDomainMask for the domain parameter, and a URL for the url parameter 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.

Chris Frederick
  • 5,482
  • 3
  • 36
  • 44
9

A Swift 3 one-liner inspired by the UUID based Swift 2 answer:

let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString)
Community
  • 1
  • 1
mz2
  • 4,672
  • 1
  • 27
  • 47
7

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")
boherna
  • 659
  • 8
  • 7
4

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

zaph
  • 111,848
  • 21
  • 189
  • 228
4

I like the idea of this article: NSTemporary​Directory - 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)
}
palme
  • 2,499
  • 2
  • 21
  • 38
1

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
neoneye
  • 50,398
  • 25
  • 166
  • 151
-1

just my two cents, comparing iOS and MacOS (sandboxed)

code:

let tmp_path1 = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first
let tmp_path2 = NSTemporaryDirectory()

print("\n--\n", tmp_path1!, "\n", tmp_path2)

we got:

> -- MacOs /Users/ingconti/Library/Containers/com.ingconti.ZipArchiveDebugAppMacOs/Data/Library/Caches
> /var/folders/40/8w6s0bsn4d55d9cjf16sjz440000gp/T/com.ingconti.ZipArchiveDebugAppMacOs/
> 
> 
> iOS simulator 
> /Users/ingconti/Library/Developer/CoreSimulator/Devices/3AC9CBFC-5535-4213-9394-523DA82B4637/data/Containers/Data/Application/82EFDDFA-D541-4791-B76F-D754116F43C9/Library/Caches
> /Users/ingconti/Library/Developer/CoreSimulator/Devices/3AC9CBFC-5535-4213-9394-523DA82B4637/data/Containers/Data/Application/82EFDDFA-D541-4791-B76F-D754116F43C9/tmp/
> 
> iOS 
> /var/mobile/Containers/Data/Application/9F9EA2D1-7CCD-48B3-85C1-957FDF0D1925/Library/Caches
> /private/var/mobile/Containers/Data/Application/9F9EA2D1-7CCD-48B3-85C1-957FDF0D1925/tmp/
> 
> or something similar on Your machine

to point out: it's better leave iOS / macOS use and reclaim space on its own, so I honestly prefer to stick with built in solution.

If temporary means "delete when no need" better lave apple do so. If You start build your own logic: a) you have to remember to claim space manually b) apple APIS have better chances to remain stable, you solution building a folder by hand can stop working (it' what happened to an macOS App if mine when sandbox came out)

IF you need ad UNIQUE name, si very different, in these case you can use proposed solution adding for example:

let uuid = UUID().uuidString
ingconti
  • 10,876
  • 3
  • 61
  • 48
  • While this info may be useful, it makes no attempt to answer the question. The question is asking how to generate a temporary file path, not how to access a folder. The question even states that `NSTemporaryDirectory()` is not what is wanted. – HangarRash Apr 24 '23 at 15:52
  • I DO know what is asked, I simply point out NO NEED. I will add a note. – ingconti Apr 24 '23 at 16:10
  • Just read your update. Only the last paragraph applies to the question. But that was covered in the other answers so there doesn't seem to be any need for this answer. – HangarRash Apr 24 '23 at 16:54