tmlen
tmlen

Reputation: 9090

Temporary file path using swift

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.

Upvotes: 34

Views: 39901

Answers (10)

Chen-Hai Teng
Chen-Hai Teng

Reputation: 753

If your project target on newer platform:

macOS 13.0, iOS 16.0

URL.temporaryDirectory.appending(path:UUID().uuidString, directoryHint: .notDirectory)

or you can create an more compatible extension as following:

Backward Compatible Version

extension URL {
    static var compatibleTemporary: Self {
        if #available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) {
            URL.temporaryDirectory
        } else {
            URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
        }
    }
}

Also, here is a compatible version of appending function:

extension URL {
    func compatibleAppending(path: String, isDirectory: Bool? = nil) -> URL {
        if #available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) {
            var hint: DirectoryHint = .inferFromPath
            if let isDirectory = isDirectory {
                hint = isDirectory ? .isDirectory : .notDirectory
            }
            return appending(path: path, directoryHint: hint)
        } else {
            if let isDirectory = isDirectory {
                return URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: isDirectory)
            }
            return URL(fileURLWithPath: NSTemporaryDirectory())
        }
    }
}

Usage:

URL.compitableTemporary.compitableAppending(path: UUID().uuidString)

Upvotes: 1

ingconti
ingconti

Reputation: 11666

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

Upvotes: -1

boherna
boherna

Reputation: 819

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")

Upvotes: 7

Chris Frederick
Chris Frederick

Reputation: 5584

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.

Upvotes: 23

Martin R
Martin R

Reputation: 540045

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)))
}

Upvotes: 31

palme
palme

Reputation: 2629

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)
}

Upvotes: 4

Code Different
Code Different

Reputation: 93191

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])

Upvotes: 42

neoneye
neoneye

Reputation: 52231

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

Upvotes: 1

mz2
mz2

Reputation: 4702

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

let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString)

Upvotes: 9

zaph
zaph

Reputation: 112875

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

Upvotes: 4

Related Questions