Reputation: 9090
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
Reputation: 753
If your project target on newer platform:
URL.temporaryDirectory.appending(path:UUID().uuidString, directoryHint: .notDirectory)
or you can create an more compatible extension as following:
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())
}
}
}
URL.compitableTemporary.compitableAppending(path: UUID().uuidString)
Upvotes: 1
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
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
Reputation: 5584
Although NSTemporaryDirectory()
does indeed return a temporary directory path for the current user, the documentation includes the following caveat:
See the
FileManager
methodurl(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 thedirectory
parameter,userDomainMask
for thedomain
parameter, and a URL for theurl
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
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
Reputation: 2629
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)
}
Upvotes: 4
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
Reputation: 52231
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)
}
}
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
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
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