16

I was trying to access temp directory in Swift. In Objective-C, I could use the following code to do so:

- (NSString *)tempDirectory {

    NSString *tempDirectoryTemplate =
    [NSTemporaryDirectory() stringByAppendingPathComponent:@"XXXXX"];
    const char *tempDirectoryTemplateCString = [tempDirectoryTemplate fileSystemRepresentation];
    char *tempDirectoryNameCString           = (char *)malloc(strlen(tempDirectoryTemplateCString) + 1);
    strcpy(tempDirectoryNameCString, tempDirectoryTemplateCString);
    char *result                             = mkdtemp(tempDirectoryNameCString);
    if (!result) {
        return nil;
    }
    NSString *tempDirectoryPath = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:tempDirectoryNameCString length:strlen(result)];
    free(tempDirectoryNameCString);
    return tempDirectoryPath;
}

However, I'm a bit confuse about the type conversion and casting from Objective-C to Swift, such as const char * or CMutablePointer<CChar>. Is there any documents that I should look into?

Thanks.

Cai
  • 3,609
  • 2
  • 19
  • 39

6 Answers6

17

How about something like :

public extension FileManager {
    func createTempDirectory() throws -> String {
        let tempDirectory = (NSTemporaryDirectory() as NSString).appendingPathComponent(UUID().uuidString)
        try FileManager.default.createDirectory(atPath: tempDirectory,
                                                withIntermediateDirectories: true,
                                                attributes: nil)
        return tempDirectory
    }
}

It doesn't answer your question about char* but it's cleaner...

NSFileManager reference here.

Also check out this SO question regarding unique names.

Nikolay Shubenkov
  • 3,133
  • 1
  • 29
  • 31
Grimxn
  • 22,115
  • 10
  • 72
  • 85
  • 3
    Note that the BSD function `mkdtemp()` is different from the NSFileManager `createDirectoryAtPath` method because it creates a *unique directory name* from the given template. – Martin R Jun 18 '14 at 09:32
  • 1
    @Grimxn Thanks! I'll try it later. BTW seems `createDirectoryAtPath(path: String!, attributes: NSDictionary!)` is deprecated, now it's `createDirectoryAtURL:withIntermediateDirectories:attributes:error:`. And does that means I could use `createFileAtPath()` to create a temp file with the same method? – Cai Jun 18 '14 at 09:39
  • @MartinR so I'd need to create my unique name if I use createDirectoryAtPath? – Cai Jun 18 '14 at 09:41
  • Check out the SO link I added to my answer - they deal with creating unique names. – Grimxn Jun 18 '14 at 09:42
  • @Grimxn I did something like `NSTemporaryDirectory().stringByAppendingPathComponent(NSUUID.UUID().UUIDString.substringToIndex(5))`. Thanks again! – Cai Jun 18 '14 at 09:46
  • 1
    You should not use error as Pointer. You should use var error: NSError and use the ampersand to get the address, so you can use the error. How do you dereference the NSErrorPointer? – Binarian Jun 27 '14 at 07:46
11

According to Apple, use of NSTemporaryDirectory is discouraged:

See the FileManager method url(for:in:appropriateFor:create:) for the preferred means of finding the correct temporary directory. For more information about temporary files, see File System Programming Guide.

So instead, you should use FileManager.default.temporaryDirectory

or if you want an unique path:

let extractionPath = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)

Antzi
  • 12,831
  • 7
  • 48
  • 74
  • https://developer.apple.com/documentation/foundation/1409211-nstemporarydirectory#discussion is the primary source that backs up this answer. – Xavier L. Jun 11 '23 at 21:26
8

Swift 2.1 version:

func createTempDirectory() -> String? {

    let tempDirURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent("XXXXXX")

    do {
        try NSFileManager.defaultManager().createDirectoryAtURL(tempDirURL, withIntermediateDirectories: true, attributes: nil)
    } catch {
        return nil
    }

    return tempDirURL.absoluteString
}
Vladimir Kofman
  • 1,983
  • 21
  • 21
5

Swift 3 and up

I think a good way to do this in swift is with an extension on FileManager. This should create a unique temporary folder and return the URL to you.

extension FileManager{

    func createTemporaryDirectory() throws -> URL {
        let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString)

        try createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
        return url
    }
}
Oskar
  • 3,625
  • 2
  • 29
  • 37
user1687195
  • 9,058
  • 2
  • 15
  • 15
4

Swift 3 version

func createTempDirectory() -> String? {

    guard let tempDirURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("myTempFile.xxx") else {
        return nil
    }

    do {
        try FileManager.default.createDirectory(at: tempDirURL, withIntermediateDirectories: true, attributes: nil)
    } catch {
        return nil
    }

    return tempDirURL.absoluteString
}
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
  • 2
    A couple of things for other people wanting to use this: 1> You've made a URL with a file extension, and are creating a folder at it, and 2>, this will return nil if the directory already exists, which might be the behaviour you (and the original poster) want, but in most cases the user will probably want a result even if the directory already exists. (Both of these things are untested, but unless Swift behaves differently to Obj-C this is the results I would expect) – narco Jan 19 '17 at 10:38
2

A direct translation of your Objective-C code to Swift would be:

func tempDirectory()->String! {
    let tempDirectoryTemplate = NSTemporaryDirectory()  + "XXXXX"
    var tempDirectoryTemplateCString = tempDirectoryTemplate.fileSystemRepresentation().copy()
    let result : CString = reinterpretCast(mkdtemp(&tempDirectoryTemplateCString))
    if !result {
        return nil
    }
    let fm = NSFileManager.defaultManager()
    let tempDirectoryPath = fm.stringWithFileSystemRepresentation(result, length: Int(strlen(result)))
    return tempDirectoryPath
}

It uses the same mkdtemp() BSD method as your original code. This method creates a directory name from the template which is guaranteed not to exist at the time where the method is called.

Thanks to Nate Cook who figured out that reinterpretCast() can be used to treat the UnsafePointer<CChar> returned by mkdtemp() as a CString, so that it can be passed to stringWithFileSystemRepresentation(), see Working with C strings in Swift, or: How to convert UnsafePointer<CChar> to CString.


As of Xcode 6 beta 6, the reinterpretCast() is not necessary anymore and the above code can be simplified to

func tempDirectory()->String! {
    let tempDirectoryTemplate = NSTemporaryDirectory()  + "XXXXX"
    var tempDirectoryTemplateCString = tempDirectoryTemplate.fileSystemRepresentation()
    let result = mkdtemp(&tempDirectoryTemplateCString)
    if result == nil {
        return nil
    }
    let fm = NSFileManager.defaultManager()
    let tempDirectoryPath = fm.stringWithFileSystemRepresentation(result, length: Int(strlen(result)))
    return tempDirectoryPath
}
Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382