Odd behavior of NSItemReplacementDirectory

  • The behavior of NSItemReplacementDirectory is contrary to the docs and sort of suboptimal. Am I missing something or are the docs just wrong?

    (1) The docs say the ‘appropriateForURL’ parameter is "a directory inside of which you want to create a unique temporary directory”. However, that isn’t the actual behavior on either platform:
    * On iOS 5, it’s created next to, not inside, the specified directory.
    * On Mac OS 10.7, it’s created in the default per-user temporary directory (/private/var/folders/...)
    (2) Despite the fact that this is called a “temporary” directory, the documented location (and actual location on iOS) implies that it won’t be automatically deleted later on if my app crashes or otherwise forgets to clean up. That kind of defeats the purpose of using a temp dir!
    (3) When called multiple times with the same ‘appropriateForURL’ path, it doesn’t return the same directory, but creates a new directory every time. This is cluttering up my filesystem with lots of empty directories named e.g. "(A Document Being Saved By MyAppName 21)”.

    Here’s the code I’m using to create the temp dir:

            NSURL* parentURL = [NSURL fileURLWithPath: _path isDirectory: YES];
            NSURL* tempDirURL = [[NSFileManager defaultManager]
                                                    URLForDirectory: NSItemReplacementDirectory
                                                    inDomain: NSUserDomainMask
                                                    appropriateForURL: parentURL
                                                    create: YES error: &error];

    Behavior (3) is leading me to suspect that I’m supposed to delete the directory when I’m done with it. Is that true? Also, if I get a new directory every time, I presumably can assume that it’s always empty and so I don’t have to worry about generating a unique filename for my temp file. Can I rely on that?

    (My use case, FYI, is very simple: I’m downloading a file via a NSURLConnection. I don’t want to put the file in its final location until it’s completely downloaded, to avoid having an incomplete file left over if something goes wrong. So I want a temporary path to place it at that won’t be visible to the user, and will eventually be deleted if the app leaves it behind, but is guaranteed to be on the same filesystem as the final location so I can move it cheaply when it’s done. Basically all I want is a Cocoa equivalent of the old FindFolder call that returns the per-volume temp directory.)

    —Jens
  • On Feb 9, 2012, at 2:38 PM, Jens Alfke wrote:

    > The behavior of NSItemReplacementDirectory is contrary to the docs and sort of suboptimal. Am I missing something or are the docs just wrong?
    >
    > (1) The docs say the ‘appropriateForURL’ parameter is "a directory inside of which you want to create a unique temporary directory”. However, that isn’t the actual behavior on either platform:
    > * On iOS 5, it’s created next to, not inside, the specified directory.
    > * On Mac OS 10.7, it’s created in the default per-user temporary directory (/private/var/folders/...)

    From my understanding of this method, the docs appear to be wrong.  I note that they have also changed from the Snow Leopard docset which I have in my Xcode, although those were more garbled than clearer.

    My understanding is that NSItemReplacementDirectory and the appropriateForURL parameter are intended for when you're going to swap the temporary file into its permanent location (which may be occupied by an existing file) using -replaceItemAtURL:withItemAtURL:backupItemName:options:resultingItemURL:error:.  So, the "appropriate" URL is that of the item you're going to replace or, if there's no such item, the final URL to which you're going to store the temporary file.

    I suspect, but haven't verified, that on Mac OS X 10.7 it's not always in /var/folders.  The reason is that, if the appropriate URL names a different volume or file system, then it's not possible to do an atomic replace or move operation from there.  I can verify that on 10.6, the resulting URL is sensitive to the appropriate URL.  If it's on the boot drive, then the result is in /var/folders.  If it's on the separate volume where I keep my home folder, then it's in ~/Library/Caches/TemporaryItems.  If it's on a different volume, then it's somewhere else, like /Volume/<the volume>/.TemporaryItems/folders.<user ID>/TemporaryItems.

    > (2) Despite the fact that this is called a “temporary” directory, the documented location (and actual location on iOS) implies that it won’t be automatically deleted later on if my app crashes or otherwise forgets to clean up. That kind of defeats the purpose of using a temp dir!

    I'm not sure about that.  The Temporary Items folder (in all of its locations) has traditionally been handled specially on the Mac, but not quite how you're thinking.  On the next login, the contents should be moved to a "Recovered files" folder in the Trash.  They are not simply deleted.  This implies that the system considers Temporary Items of interest to the user, which is why it "recovers" them and puts them someplace the user may find them.  It also implies that the app should clean up its temporary items in normal operation.  If they were left there, it is assumed that a crash or other disorderly exit prevented the app from saving them for the user, which is why they are "recovered".

    Maybe iOS treats Temporary Items differently.  I don't know.

    There's a separate location, called the "Chewable Items" folder (a.k.a. Cleanup at Startup), which is deleted on the next login.  To my knowledge, this special folder hasn't been exposed via Cocoa API and is only obtainable using FindFolder / FSFindFolder.

    > (3) When called multiple times with the same ‘appropriateForURL’ path, it doesn’t return the same directory, but creates a new directory every time. This is cluttering up my filesystem with lots of empty directories named e.g. "(A Document Being Saved By MyAppName 21)”.

    > Behavior (3) is leading me to suspect that I’m supposed to delete the directory when I’m done with it. Is that true?

    I would say no.  The specifics of the directory are an implementation detail, presumably to aid the user with a helpful name in the event that the directory ever ends up in Recovered items.  The documentation, for all its flaws, is still the best you have for a design contract and it doesn't say you own the resulting directory (although it does say it's new).

    If you're always supplying an appropriate URL in the same place every time, you may wish to make the query only once and then use the resulting directory throughout your app's lifetime instead of querying it for each file.

    > Also, if I get a new directory every time, I presumably can assume that it’s always empty and so I don’t have to worry about generating a unique filename for my temp file. Can I rely on that?

    The documentation does say it's a new directory.  That's new since the 10.6 docset, though, and I'd be a bit wary of it, depending on where you're deploying to.

    > (My use case, FYI, is very simple: I’m downloading a file via a NSURLConnection. I don’t want to put the file in its final location until it’s completely downloaded, to avoid having an incomplete file left over if something goes wrong. So I want a temporary path to place it at that won’t be visible to the user, and will eventually be deleted if the app leaves it behind, but is guaranteed to be on the same filesystem as the final location so I can move it cheaply when it’s done. Basically all I want is a Cocoa equivalent of the old FindFolder call that returns the per-volume temp directory.)

    As I say, that's not the equivalent of FindFolder's Temporary Items folder, it's the equivalent of the Chewable Items folder.  If you were targeting Mac OS X, I'd recommend still using FSFindFolder for that.  I don't know what to recommend for iOS.

    Regards,
    Ken
previous month february 2012 next month
MTWTFSS
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29        
Go to today