45

I need a quick and easy way to store files with unique file names on iOS. I need to prefix the file with a string, and then append the generated unique identifier to the end. I was hoping NSFileManager had some convenient method to do this, but I can't seem to find it.

I was looking at createFileAtPath:contents:attributes:, but am unsure if the attributes will give me that unique file name.

jscs
  • 63,694
  • 13
  • 151
  • 195
spentak
  • 4,627
  • 15
  • 62
  • 90
  • 1
    This [SO][1] might be is what you need. [1]: http://stackoverflow.com/questions/215820/how-do-i-create-a-temporary-file-with-cocoa – user523234 Oct 13 '11 at 19:24

9 Answers9

79

Create your own file name:

CFUUIDRef uuid = CFUUIDCreate(NULL);
CFStringRef uuidString = CFUUIDCreateString(NULL, uuid);
CFRelease(uuid);
NSString *uniqueFileName = [NSString stringWithFormat:@"%@%@", prefixString, (NSString *)uuidString];
CFRelease(uuidString);

A simpler alternative proposed by @darrinm in the comments:

NSString *prefixString = @"MyFilename";

NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString] ;
NSString *uniqueFileName = [NSString stringWithFormat:@"%@_%@", prefixString, guid];

NSLog(@"uniqueFileName: '%@'", uniqueFileName);

NSLog output:
uniqueFileName: 'MyFilename_680E77F2-20B8-444E-875B-11453B06606E-688-00000145B460AF51'

Note: iOS6 introduced the NSUUID class which can be used in place of CFUUID.

NSString *guid = [[NSUUID new] UUIDString];
zaph
  • 111,848
  • 21
  • 189
  • 228
  • 13
    A nice shortcut to get a GUID string is: NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString] – darrinm Aug 02 '12 at 22:07
  • 2
    That's great, but what is the probability of collisions? – Arsynth Sep 27 '13 at 05:55
  • @user578205 From the docs: A UUID is made unique over both space and time by combining a value unique to the computer on which it was generated and a value representing the number of 100-nanosecond intervals. There is a reason they are called "unique". – zaph Sep 27 '13 at 10:19
  • And if UUIDs will be generated in the cycle, the function srand () to generate a UUID will help solve the problem? – Arsynth Sep 27 '13 at 10:37
  • 2
    The only potential problem is if you generate the UUIDs faster than every 100 nanoseconds which on todays processors is probably not possible. Also srand() and it's family are not really good at random numbers, arc4random() is the way to go for random numbers. – zaph Sep 27 '13 at 10:43
10

Super-easy Swift 4 1-liner:

fileName = "MyFileName_" + UUID().uuidString

or

fileName = "MyFileName_" + ProcessInfo().globallyUniqueString
drewster
  • 5,460
  • 5
  • 40
  • 50
9

I use current date to generate random file name with a given extension. This is one of the methods in my NSFileManager category:

+ (NSString*)generateFileNameWithExtension:(NSString *)extensionString
{
    // Extenstion string is like @".png"

    NSDate *time = [NSDate date];
    NSDateFormatter* df = [NSDateFormatter new];
    [df setDateFormat:@"dd-MM-yyyy-hh-mm-ss"];
    NSString *timeString = [df stringFromDate:time];
    NSString *fileName = [NSString stringWithFormat:@"File-%@%@", timeString, extensionString];

    return fileName;
}
Denis Kutlubaev
  • 15,320
  • 6
  • 84
  • 70
  • 3
    WARNING: Using the date can cause collisions during daylight savings time -1. (In the fall when the same hour is repeated.) – bob Mar 11 '14 at 17:26
  • 1
    To prevent collisions when daylight savings time changes we can append "Z" specifier to the date format string (it represents current time zone offset). Maybe this will help someone. – Stan Mots Apr 16 '16 at 13:47
  • Unless you want the date to be part of the fileName, then this should be avoided because of collisions as Bob mentions, that initializing a `Date` object is an expensive and that Apple already provides alternative ways of creating a unique identifier – mfaani Feb 27 '20 at 14:09
7

You can also use the venerable mktemp() (see man 3 mktemp). Like this:

- (NSString*)createTempFileNameInDirectory:(NSString*)dir
{
  NSString* templateStr = [NSString stringWithFormat:@"%@/filename-XXXXX", dir];
  char template[templateStr.length + 1];
  strcpy(template, [templateStr cStringUsingEncoding:NSASCIIStringEncoding]);
  char* filename = mktemp(template);

  if (filename == NULL) {
    NSLog(@"Could not create file in directory %@", dir);
    return nil;
  }
  return [NSString stringWithCString:filename encoding:NSASCIIStringEncoding];
}

The XXXXX will be replaced with a unique letter/number combination. They can only appear at the end of the template, so you cannot have an extension appended in the template (though you can append it after the unique file name is obtained). Add as many X as you want in the template.

The file is not created, you need to create it yourself. If you have multiple threads creating unique files in the same directory, you run the possibility of having race conditions. If this is the case, use mkstemp() which creates the file and returns a file descriptor.

ovidiu
  • 1,129
  • 1
  • 10
  • 10
  • 2
    You can actually have an extension.. `path = @".../XXXXXX.pdf"`and then do `mkstemps(path, suffixLength)`. `suffixLength = 4` in this case. – Marc Oct 30 '13 at 08:48
5

In iOS 6 the simplest method is to use:

NSString *uuidString = [[NSUUID UUID] UUIDString];
Reefwing
  • 2,242
  • 1
  • 22
  • 23
5

Here is what I ended up using in Swift 3.0

public func generateUniqueFilename (myFileName: String) -> String {

    let guid = ProcessInfo.processInfo.globallyUniqueString
    let uniqueFileName = ("\(myFileName)_\(guid)")

    print("uniqueFileName: \(uniqueFileName)")

    return uniqueFileName
}
Dave Levy
  • 1,036
  • 13
  • 20
3

Swift 4.1 and 5. Just pass you file extension name and function will return unique file name.

func uniqueFileNameWithExtention(fileExtension: String) -> String {
        let uniqueString: String = ProcessInfo.processInfo.globallyUniqueString
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyyMMddhhmmsss"
        let dateString: String = formatter.string(from: Date())
        let uniqueName: String = "\(uniqueString)_\(dateString)"
        if fileExtension.length > 0 {
            let fileName: String = "\(uniqueName).\(fileExtension)"
            return fileName
        }
        
        return uniqueName
    }
Gurjinder Singh
  • 9,221
  • 1
  • 66
  • 58
2

Swift 4.2, I use two options, one mostly unique but readable, and the other just unique.

// Create a unique filename, added to a starting string or not
public func uniqueFilename(filename: String = "") -> String {
    let uniqueString = ProcessInfo.processInfo.globallyUniqueString
    return filename + "-" + uniqueString
}

// Mostly Unique but Readable ID based on date and time that is URL compatible ("unique" to nearest second)
public func uniqueReadableID(name: String = "") -> String {

    let timenow = DateFormatter.localizedString(from: Date(), dateStyle: .medium, timeStyle: .medium)
    let firstName = name + "-" + timenow
    do {
        // Make ID compatible with URL usage
        let regex = try NSRegularExpression(pattern: "[^a-zA-Z0-9_]+", options: [])
        let newName = regex.stringByReplacingMatches(in: firstName, options: [], range: NSMakeRange(0, firstName.count), withTemplate: "-")
        return newName
    }
    catch {
        print(" Unique ID Error: \(error.localizedDescription)")
        return uniqueFilename(filename: name)
    }
}
Golompse
  • 81
  • 2
  • 5
2

This should probably work for you:

http://vgable.com/blog/2008/02/24/creating-a-uuid-guid-in-cocoa/

The author of the post suggests implementing a 'stringWithUUID' method as a category of NSString. Just append a GUID generated with this method to the end of the file name that you're creating.

lottscarson
  • 578
  • 5
  • 14