Swift中的临时文件路径

32
如何在Mac OS X上使用Swift/Cocoa获得唯一临时文件路径?
Cocoa似乎没有提供此功能,只有NSTemporaryDirectory()可返回临时目录的路径。使用BSD mktemp函数需要一个可变的C字符串作为参数。
9个回答

39

苹果一直在努力摆脱路径字符串,并转向使用 NSURL。以下是一种方式:

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

30

以下是在 Swift 3 及以上版本 中使用 mkstemp() 的可能方法。使用 URL 方法可在 URL 实例与表示文件系统路径的 C 字符串之间进行转换:

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

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

2
不错。我认为你应该使用mkstemp作为示例,而不是mktemp,因为它通常更安全。 - jtbandes
1
需要从mkstemp中关闭文件描述符fd吗? - tmlen
4
@tmlen: 是的,否则会出现文件描述符泄漏。或者,使用 let fh = NSFileHandle(fileDescriptor: fd, closeOnDealloc: true) 创建一个文件句柄来转移所有权。当文件句柄被释放时,fd将被关闭。 - Martin R

19

虽然NSTemporaryDirectory()确实返回当前用户的临时目录路径,但文档中包含以下警告:

请参见FileManager方法url(for:in:appropriateFor:create:)以查找正确的临时目录的首选方法。

点击链接后,会出现以下内容:

您可以使用此方法创建新的临时目录。为此,请将directory参数指定为FileManager.SearchPathDirectory.itemReplacementDirectorydomain参数指定为userDomainMask,并指定一个URL作为url参数,用于确定返回的URL的卷。

例如,以下代码会生成一个新的临时目录,并具有类似于/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.
}
(请注意,在创建临时目录时,create参数将被忽略。)所以这两种方法之间到底有什么区别呢?当我从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)/"
看起来 NSTemporaryDirectory() 总是会返回当前用户的临时目录路径,而 FileManagerurl(for:appropriateFor:create) 方法每次调用都会返回一个新的临时子目录。例如,这里显示了从 Swift REPL 连续调用 url(for:in:appropriateFor:create:) 所返回的目录:
  • 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)/
以下是从 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)/
NSHipster 的临时文件文章 似乎暗示 FileManager 方法 url(for:in:appropriateFor:create:) 是用于在将文件移动到更永久的位置(例如上面示例中的用户桌面)之前进行分段的,但我不明白为什么它不能被用来获取一个唯一的子目录,在你完成后会自动删除,而你不必担心其他进程写入相同临时目录时可能意外覆盖文件。

2
感谢您的分析。像这样的答案使得SO成为获取答案的最佳场所。 - mojuba
@mojuba 很高兴你觉得有帮助!☺️ - Chris Frederick

9

基于UUID的Swift 2答案 的启发,这是一个Swift 3的一行代码:

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

7

Swift中的文件管理器扩展,可获取临时文件URL。如有需要,可以传递自己的文件名和扩展名。

public extension FileManager {

    func temporaryFileURL(fileName: String = UUID().uuidString) -> URL? {
        return URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent(fileName)
    }
}

用法:

let tempURL = FileManager.default.temporaryFileURL()
let tempJPG = FileManager.default.temporaryFileURL(fileName: "temp.jpg")

4
我喜欢这篇文章的想法:NSTemporaryDirectory - NSHipster 这里使用了NSTemporaryDirectory()作为临时文件夹,使用ProcessInfo.processInfo.globallyUniqueString生成唯一字符串。
Swift 4:
func uniqueTempFolderURL() -> URL
{
    let folderName = ProcessInfo.processInfo.globallyUniqueString
    return URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent(folderName)
}

4

使用全球唯一标识符 (GUID):

let directory :NSString = "directory"
let randomName = NSProcessInfo().globallyUniqueString
let path = directory.stringByAppendingPathComponent(randomName)

目录/3B635E49-813A-4324-B4B8-56279B42BEAB-36687-0002D962615DAE5F


1

Swift3

我来到这里是为了寻找类似于boost::filesystem::unique_path()的东西。

因此,我创建了这个扩展来增强URL类的功能。

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

添加 NSTemporaryDirectory(),这是最佳答案。 - Nike Kov

-1

只是我的个人看法,比较iOS和MacOS(沙盒)

代码:

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

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

我们得到了:

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

指出一下: 最好让iOS / macOS自己管理空间,所以我更喜欢使用内置解决方案。
如果临时意味着“不需要时删除”,最好让苹果自己处理。 如果您开始构建自己的逻辑: a)您必须手动声明空间 b)苹果API有更好的稳定性,而您手动构建文件夹的解决方案可能会停止工作(当沙盒出现时,我的macOS应用程序就是这种情况)
如果您需要一个独特的名称,则非常不同,在这种情况下,您可以使用提议的解决方案,例如:
let uuid = UUID().uuidString

虽然这些信息可能有用,但它并没有试图回答问题。问题是要求如何生成临时文件路径,而不是如何访问文件夹。问题甚至指出 NSTemporaryDirectory() 不是所需的内容。 - HangarRash
我知道被问的是什么,我只是指出不需要。我会添加一条注释。 - ingconti
刚刚看到你的更新。只有最后一段适用于这个问题。但是其他答案已经涵盖了这一点,所以似乎没有必要再回答一遍。 - HangarRash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接