45

Years ago when I was working with C# I could easily create a temporary file and get its name with this function:

Path.GetTempFileName();

This function would create a file with a unique name in the temporary directory and return the full path to that file.

In the Cocoa API's, the closest thing I can find is:

NSTemporaryDirectory

Am I missing something obvious or is there no built in way to do this?

Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
lfalin
  • 4,219
  • 5
  • 31
  • 57
  • Word of caution when using that C# API: it has a namespace of only 65k files and will throw an exception once that is exhausted. This has happened to us in production - not all programs diligently clean up their temp files. – fzwo Dec 25 '16 at 09:18

11 Answers11

36

A safe way is to use mkstemp(3).

benji
  • 5
  • 2
  • 1
    Not exactly "with Cocoa"... more like "with POSIX" – Ky - Mar 01 '19 at 23:16
  • The documentation says the following: "_The implementation of these functions calls arc4random(3), which is not reentrant. You must provide your own locking around this and other consumers of the arc4random(3) API._" I don't see how I'm supposed to synchronize the usage of all arc4random consumers in my project and all the 3rd party libraries it's using. – itotsev Jun 07 '22 at 11:24
21

[Note: This applies to the iPhone SDK, not the Mac OS SDK]

From what I can tell, these functions aren't present in the SDK (the unistd.h file is drastically pared down when compared to the standard Mac OS X 10.5 file). I would use something along the lines of:

[NSTemporaryDirectory() stringByAppendingPathComponent: [NSString stringWithFormat: @"%.0f.%@", [NSDate timeIntervalSinceReferenceDate] * 1000.0, @"txt"]];

Not the prettiest, but functional

Luke
  • 11,426
  • 43
  • 60
  • 69
Ben Gottlieb
  • 85,404
  • 22
  • 176
  • 172
  • 5
    This has the same race condition as mktemp(3): Separating creation of the filename from creation of the temporary file opens a window of vulnerability. Use mkstemp(3) as Graham Lee suggests. – Chris Hanson Oct 19 '08 at 03:54
  • Sorry, I was in iPhone mode; mkstemp(3) as suggested by the original poster is fine, but it won't work on iPhone. – Ben Gottlieb Oct 19 '08 at 11:44
  • 2
    On the iPhone, each app has its own subtree of the filesystem. NSTemporaryDirectory() returns something within the app bundle. You still race against yourself and against anything else that has write permission into your tmp dir, but I think that's only privileged processes. – Ken Oct 19 '08 at 18:07
  • 2
    @Ken where did the asker say they were on iPhone? –  Oct 19 '08 at 22:21
  • Nowhere, other than in this answer that we're commenting on. :-) I was replying to the above. The lack of mkstemp on the phone is not as much of a problem on the phone as it would be on the mac. – Ken Oct 20 '08 at 10:34
  • 2
    This failes when called "at the same time" from more than one thread. I don't know how fine grained the method is (the documentation doesn't tell details about that) but for me it happened that my NSOperations were called too fast and the files got the same names. My solution was to add the NSOperations address to the filename since they are sure to differ: [NSString stringWithFormat: @"%d_%.0f.%@", self, [NSDate timeInterv... – Christian Beer Jan 27 '11 at 11:53
  • ..or just use `NSUUID`, i.e. `NSString *uuidString = [[NSUUID UUID] UUIDString];` – Jay Mar 24 '17 at 07:03
17

Apple has provided an excellent way for accessing temp directory and creating unique names for the temp files.

- (NSString *)pathForTemporaryFileWithPrefix:(NSString *)prefix
{
    NSString *  result;
    CFUUIDRef   uuid;
    CFStringRef uuidStr;

    uuid = CFUUIDCreate(NULL);
    assert(uuid != NULL);

    uuidStr = CFUUIDCreateString(NULL, uuid);
    assert(uuidStr != NULL);

    result = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-%@", prefix, uuidStr]];
    assert(result != nil);

    CFRelease(uuidStr);
    CFRelease(uuid);

    return result;
}

LINK :::: http://developer.apple.com/library/ios/#samplecode/SimpleURLConnections/Introduction/Intro.html#//apple_ref/doc/uid/DTS40009245 see file :::AppDelegate.m

Luke
  • 11,426
  • 43
  • 60
  • 69
muzz
  • 4,324
  • 2
  • 24
  • 14
11

If targeting iOS 6.0 or Mac OS X 10.8 or higher:

NSString *tempFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
fzwo
  • 9,842
  • 3
  • 37
  • 57
11

I created a pure Cocoa solution by way of a category on NSFileManager that uses a combination of NSTemporary() and a globally unique ID.

Here the header file:

@interface NSFileManager (TemporaryDirectory)

-(NSString *) createTemporaryDirectory;

@end

And the implementation file:

@implementation NSFileManager (TemporaryDirectory)

-(NSString *) createTemporaryDirectory {
 // Create a unique directory in the system temporary directory
 NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString];
 NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:guid];
 if (![self createDirectoryAtPath:path withIntermediateDirectories:NO attributes:nil error:nil]) {
  return nil;
 }
 return path;
}

@end

This creates a temporary directory but could be easily adapted to use createFileAtPath:contents:attributes: instead of createDirectoryAtPath: to create a file instead.

Philipp
  • 891
  • 1
  • 11
  • 12
6

Swift 5 and Swift 4.2

import Foundation

func pathForTemporaryFile(with prefix: String) -> URL {
    let uuid = UUID().uuidString
    let pathComponent = "\(prefix)-\(uuid)"
    var tempPath = URL(fileURLWithPath: NSTemporaryDirectory())
    tempPath.appendPathComponent(pathComponent)
    return tempPath
}

let url = pathForTemporaryFile(with: "blah")
print(url)
// file:///var/folders/42/fg3l5j123z6668cgt81dhks80000gn/T/johndoe.KillerApp/blah-E1DCE512-AC4B-4EAB-8838-547C0502E264

Or alternatively Ssswift's oneliner:

let prefix = "blah"
let url2 = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("\(prefix)-\(UUID())")
print(url2)
neoneye
  • 50,398
  • 25
  • 166
  • 151
Bart van Kuik
  • 4,704
  • 1
  • 33
  • 57
  • 2
    This code no longer works, but an equivalent version is even simpler: `URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("\(prefix)-\(UUID())")` – Ssswift Jun 14 '17 at 19:40
  • Thanks @Ssswift updated the answer in a slightly more verbose manner. – Bart van Kuik Jun 16 '17 at 05:59
2

You could use mktemp to get a temp filename.

Giao
  • 14,725
  • 2
  • 24
  • 18
  • 5
    There's a race condition in mktemp(3), it's better to use mkstemp(3). –  Oct 19 '08 at 01:38
2

The modern way to do this is FileManager's url(for:in:appropriateFor:create:).

With this method, you can specify a SearchPathDirectory to say exactly what kind of temporary directory you want. For example, a .cachesDirectory will persist between runs (as possible) and be saved in the user's library, while a .itemReplacementDirectory will be on the same volume as the target file.

Ssswift
  • 916
  • 10
  • 20
2

Don't use legacy APIs like NSTemporaryDirectory, get a proper URL instead from FileManager.

let tmpURL = FileManager
    .default
    .temporaryDirectory
    .appendingPathComponent(UUID().uuidString)

You'd still have to create the directory.

0

You could use an NSTask to uuidgen to get a unique file name, then append that to a string from NSTemporaryDirectory(). This won't work on Cocoa Touch. It is a bit long-winded though.

DD_
  • 7,230
  • 11
  • 38
  • 59
alextgordon
  • 163
  • 1
  • 11
  • Using `NSTask` to execute `uuidgen` is a bit overkill if all you want is a UUID. – dreamlax Oct 18 '12 at 00:54
  • Note that 10.8+ has the NSUUID class to create UUIDs w/o leaving Cocoa land: https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSUUID_Class/Reference/Reference.html – Jay Mar 14 '14 at 19:47
0

Adding to @Philipp:

- (NSString *)createTemporaryFile:(NSData *)contents {
    // Create a unique file in the system temporary directory
    NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString];
    NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:guid];
    if(![self createFileAtPath:path contents:contents attributes:nil]) {
        return nil;
    }
    return path;
}
SwiftArchitect
  • 47,376
  • 28
  • 140
  • 179