layout | title | category | rating | description |
---|---|---|---|---|
post |
NSTemporaryDirectory /<br/>NSItemReplacementDirectory /<br/>mktemp(3) |
Foundation |
7.7 |
Volumes have been written about persisting data, but when it comes to short-lived, temporary files, there is very little to go on for Objective-C. (Or if there has, perhaps it was poetically ephemeral itself). |
Volumes have been written about persisting data, but when it comes to short-lived, temporary files, there is very little to go on for Objective-C. (Or if there has, perhaps it was poetically ephemeral itself).
Temporary files are used to write a buffer to disk, to either be atomically moved to a permanent location, or processed in some manner and then discarded. Creating a temporary file involves finding the appropriate part of the filesystem, generating a unique name, and moving or deleting the file after you're finished using it.
The first step to creating temporary files or directories is to find a reasonable, out-of-the-way place to write to—somewhere that won't be backed up by Time Machine or synced to iCloud or the like.
On Unix systems, the /tmp
directory was the de facto scratch space, but with the sandboxed containers of iOS and Mac OS X apps today, a hard-coded path just won't cut it.
NSTemporaryDirectory
is a Foundation function that returns the directory designated for writing short-lived files on the targeted platform.
In recent years, Apple has pushed to extricate filesystem path operations from NSString
APIs, recommending that users switch to using NSURL
and NSURL
-based APIs for classes like NSFileManager
. Unfortunately, the migration has not been entirely smooth.
Consider the documentation for NSTemporaryDirectory
:
See the
NSFileManager
methodURLForDirectory:inDomain:appropriateForURL:create:error:
for the preferred means of finding the correct temporary directory.
Alright, fair enough. Let's see what's going on with NSFileManager -URLForDirectory:inDomain:appropriateForURL:create:error:
:
You can also use this method to create a new temporary directory for storing things like autosave files; to do so, specify
NSItemReplacementDirectory
for the directory parameter,NSUserDomainMask
for thedomain
parameter, and a valid parent directory for theurl
parameter. After locating (or creating) the desired directory, this method returns the URL for that directory.
Huh? Even after reading through that a few times, it's still unclear how to use this, or what the expected behavior. A quick search through the mailing lists reaffirms this confusion.
Actually, this method appears to be intended for moving existing temporary files to a permanent location on disk with -replaceItemAtURL:withItemAtURL:backupItemName:options:resultingItemURL:error:
. Not exactly what we're looking for.
So much for the NSString
filesystem API migration. Let's stick to something that works:
[NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES];
With a place to call home (temporarily), the next step is to figure out what to name our temporary file. We don't really care what temporary files are named—the only real concern is that they're unique, so as to not interfere with, or be interfered by, any other temporary files.
The best way to generate a unique identifier is to use the globallyUniqueString
method on NSProcessInfo
NSString *identifier = [[NSProcessInfo processInfo] globallyUniqueString];
This will return a string in the format: 5BD255F4-CA55-4B82-A555-0F4BC5CA2AD6-479-0000018E14D059CC
Other sources advise the direct invocation of the
mktemp(3)
system command in order to mitigate potential conflicts. However, usingNSProcessInfo -globallyUniqueString
to generate unique names is extremely unlikely to result in a collision.
Alternatively, NSUUID
(discussed previously) also produces workable results, assuming that you're not doing anything too crazy.
[[NSUUID UUID] UUIDString]
This produces a string in the format: 22361D15-E17B-4C48-AEA6-C73BBEA17011
Using the aforementioned technique for generating unique identifiers, we can create unique temporary file paths:
NSString *fileName = [NSString stringWithFormat:@"%@_%@", [[NSProcessInfo processInfo] globallyUniqueString], @"file.txt"];
NSURL *fileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]];
In situations where many temporary files might be created by a process, it may be a good idea to create a temporary sub-directory, which could then be removed for easy cleanup.
Creating a temporary directory is no different than any other invocation of NSFileManager -createDirectoryAtURL:withIntermediateDirectories:attributes:error:
:
NSURL *directoryURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]] isDirectory:YES];
[[NSFileManager defaultManager] createDirectoryAtURL:directoryURL withIntermediateDirectories:YES attributes:nil error:&error];
And, of course, temporary file paths relative to this directory can be created with URLByAppendingPathComponent:
:
NSURL *fileURL = [directoryURL URLByAppendingPathComponent:fileName];
Files don't exist on the file system until a particular file path is either touched or written to.
There are several ways in which data is written to disk in Foundation. The most straightforward of which is NSData -writeToURL:options:error
:
NSData *data = ...;
NSError *error = nil;
[data writeToURL:fileURL options:NSDataWritingAtomic error:&error];
For more advanced APIs, it is not uncommon to pass an NSOutputStream
instance to direct the flow of data. Again, creating an output stream to a temporary file path is no different than any other kind of file path:
NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:[fileURL absoluteString] append:NO];
The final step is what makes a temporary file actually temporary: clean up.
Although files in a system-designated temporary directory make no guarantees about how long they'll exist before being deleted automatically by the operating system (up to a few days, according to scattered reports), it's still good practice to take care of it yourself once you're finished.
Do that with NSFileManager -removeItemAtURL:
, which works for both a temporary file and a temporary directory:
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtURL:fileURL error:&error];
"This too shall pass" is a mantra that acknowledges that all things are indeed temporary. Within the context of the application lifecycle, some things are more temporary than others, and it is in that knowledge that we act appropriately, seeking to find the right place, make a unique impact, and leave without a trace.
Perhaps we can learn something from this cycle in our own, brief and glorious lifecycle.