Ad

Our DNA is written in Swift
Jump

Directories: Temp, Cache, Documents

The first thing to learn when starting to persist data onto the iPhones solid state drive is that all apps have their own sandbox. Contrary to other operating systems where you have a shared documents folder, you have several directories for each individual apps. Now on the iPhone these sandbox directories all get a GUID in their name, so you have no way to hardcode or guess the real path they will end up on.

Luckily there is a method of getting the path for those directory, which I wrote about about a year ago: Getting Standard Paths. Today I will elaborate a bit on what I learned since then, it turns out that this is only half the story and as intermediate programmer you will want to use the correct kind of folder for each task.

You have the following kinds of folders at your disposal:

  • the app bundle path when getting non-localized resources that you put in your project
  • the specific path to a localized resource in your app bundle, if your app supports multiple languages
  • the documents folder where you save data that also will get backed up.
  • the caches folder where you save data that will NOT get backed up allowing for faster interaction with iTunes
  • the temp folder, which will get automatically cleared and not backed up

The latter two are the ones I heard about only recently when watching a training video by Apple. Before then I was always writing all files into the documents. Now this is fine for regular user files that you also want to be backed up, but not for temporary files or file that you can easily download off the internet again. Those should not be kept in documents as they tend to eat up more and more space and will slow down the user’s backup via iTunes.

App Bundle

A bundle has a corresponding representation in an NSBundle instance which you can retrieve via the NSBundle class method mainBundle. On Mac apps sometimes might also consist of several bundles, but on iPhone we always have a single .app bundle.

NSBundle *bundle = [NSBundle mainBundle];
NSString *bundlePath = [bundle bundlePath];
NSLog(@"Bundle: %@", bundle);
 
NSString *someFile = [bundlePath stringByAppendingPathComponent:@"bla.plist"];
NSLog(@"File in Bundle: %@", someFile);

This makes use of a string method which takes care of adding an extra slash before the file name if necessary. Now this approach works for files that are not localized, but probably you would want to use the next method, which automatically finds the correct path to use for the currently set locale.

App Bundle (Localized)

If you are looking for a specific file, there is a technique where you don’t have to care if it is localized or not. Or if the user’s device actually is on a locale that you support.

NSBundle *bundle = [NSBundle mainBundle];
 
NSString *someFile = [bundle pathForResource:@"bla.plist" ofType:nil];
NSString *someFileToo = [bundle pathForResource:@"bla" ofType:@"plist"];
 
NSLog(@"File in Bundle: %@", someFile);
NSLog(@"File in Bundle: %@", someFileToo);

It does not matter if you put the type as nil and the name with extension in the first parameter or if you split it correctly. The second is a bit cleaner, but you get the same result. You don’t have to worry where this file is or if it’s localized, you always get a usable path back.

A shortcut method for images is this, which also gets you the correct image from the app bundle.

UIImage *image = [UIImage imageNamed:@"image.png"];

This method has the added advantage of also caching the image. So it will only go to the file system on the first time you are calling it. On subsequent times it will go to the system cache instead.

NSPathUtilities

The app bundle folder is not writable (on non-jailbroken devices). Therefore you need to use a different method if you want to save files. The NSPathUtilites provide a C-function to search for specific standard folders. You don’t need to worry about adding their header, they are loaded by default.

NSArray *writablePaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [writablePaths lastObject];
NSString *fileInDocuments = [documentsPath stringByAppendingPathComponent:@"file.plist"];
 
NSLog(@"File: %@", fileInDocuments);

To get the path for a folder where you can cache files you simply change what you’re looking for in NSSearchPathForDirectoriesInDomains. Use this whenever you are saving something to disk that can easily be downloaded again and does not need to be backed up.

NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *cacheFile = [cachesPath stringByAppendingPathComponent:@"file.plist"];
 
NSLog(@"Cache File: %@", cacheFile);

The search function always returns an NSArray, so in the documentation you usually see using the objectAtIndex:0. But personally I prefer to use lastObject instead. I don’t like objectAtIndex because in theory the resulting array could be empty which would cause an exception accessing the item at index 0. The Caches directory typically resides under /Library/Caches.

Finally, we’ll touch upon temporary files. One might suspect that you could get the perfect temporary location also via NSSearchPathForDirectoriesInDomains. One would be wrong.

For historic reasons the need for a temp directory existed in Unix long before Objective-C. So the way to get the temp directory in our language is crafted on top of an existing Unix method using confstr. It’s also in NSPathUtilities for your convenience.

NSString *tmpDirectory = NSTemporaryDirectory();
NSString *tmpFile = [tmpDirectory stringByAppendingPathComponent:@"temp.txt"];
 
NSLog(@"Temp File:%@", tmpFile);

That’s how you get the Doc, Temp and Caches directories.

Bonus: Filter Array by Extension

You can ignore the second and third parameter for NSSearchPathForDirectoriesInDomains. Those are onyl useful on OSX. If you look into the header (by CMD+doubleclicking on the function name) you find that there are a couple of interesting category extensions to NSString, allowing for manipulation of paths. Also one piqued my interest that allows to filter an NSArray for specific extensions.

Say if you have some paths in an array and you want to only get the ones matching certain extensions. Previously I would have looped through the array and put the matching items into a new one. But actually it can be as simple as shown here:

NSArray *someFiles = [NSArray arrayWithObjects:@"1.jpg", @"2.png", @"3.jpg", @"4.txt", nil];
NSArray *filterTypes = [NSArray arrayWithObject:@"jpg"]; // without dot
NSArray *filteredFiles = [someFiles pathsMatchingExtensions:filterTypes];
 
NSLog(@"%@", filteredFiles);

This will save me lots of typing in the future.

Conclusion

The methods of getting the various paths are not difficult. But the knowledge about what is the correct use case for each is what separates a Cocoa Doodler from a Cocoa Programmer. If you find – like I did – that you used to save temporary or cached files in the documents folder then you are highly encouraged to go back in and change to using the appropriate folders instead.

This will make your apps a tiny bit better and backups of your user’s iPhones a little bit faster.


Categories: Recipes

2 Comments »

Trackbacks

  1. iPhone Developer Tips | Just the Thought of It
  2. Command Line Tools Tutorial (1) | Cocoanetics

Leave a Comment