It is sometimes easy to forget that iOS is an operating system much like that running on many other computers today. Given this fact, it should come as no surprise that iOS has a file system much like any other operating system that allows applications to store persistent data on behalf of the user. Much like other platforms, the iOS file system provides a directory based structure into which files can be created and organized.
The goal of this chapter is provide an overview of how to work with directories from within an iOS 4 iPhone application. Topics covered include identifying the application’s document and temporary directories, finding the current working directory, creating, removing and renaming directories and obtaining listings of a directory’s content. Once the topic of directory management has been covered, we will move on to working with files in the chapter entitled Working with Files on iOS 4.
The Application Documents Directory
An iPhone user can install multiple applications on a single device. The iOS platform is responsible for ensuring that these applications cannot interfere with each other, both in terms of memory usage and data storage. As such, each application is restricted in terms of where it can store data on the file system of the device. iOS achieves this by allowing applications to read and write only to their own Documents and tmp directories. Within these two directories the corresponding application can create files and also sub-directories to any required level of depth. This area constitutes the application’s sandbox and the application cannot create or modify files or directories outside of these directories.
The Objective-C NSFileManager, NSFileHandle and NSData Classes
The Foundation Framework provides three classes that are indispensable when it comes to working with files and directories:
- NSFileManager - The NSFileManager class can be used to perform basic file and directory operations such as creating, moving, reading and writing files and reading and setting file attributes. In addition, this class provides methods for, amongst other tasks, identifying the current working directory, changing to a new directory, creating directories and listing the contents of a directory.
- 'NSFileHandle - The NSFileHandle class is provided for performing lower level operations on files, such as seeking to a specific position in a file and reading and writing a file's contents by a specified number of byte chunks and appending data to an existing file. This class will be used extensively in the chapter entitled Working with Files on iOS 4.
- NSData - The NSData class provides a useful storage buffer into which the contents of a file may be read, or from which dynamically stored data may be written to a file.
Understanding Pathnames in Objective-C
As with Mac OS X, iOS defines pathnames using the standard UNIX convention. As such each component of a path is separated by a forward slash (/). When an application starts, the current working directory is the file system’s root directory represented by single /. From this location, the application must navigate to its own Documents and tmp directories in order to be able to write files to the file system. Path names that begin with a / are said to be absolute path names in that they specify a file system location relative to the root directory. For example, /var/mobile is an absolute path name.
Paths that do not begin with a slash are interpreted to be relative to a current working directory. For example, if the current working directory is /User/demo and the path name is mapdata/local.xml then the file is considered to have an equivalent full, absolute pathname of /User/demo/mapdata/local.xml.
Creating an NSFileManager Instance Object
The NSFileManager class contains a class method named defaultManager that is used to create an instance of the class:
NSFileManager *filemgr;
filemgr = [NSFileManager defaultManager];
In the above example we have declared a variable named filemgr to point to an object of type NSFileManager, and then created an object of that type using the NSFileManager defaultManager class method and assigned it to the variable. Having created the object we can begin to use it to work with files and directories. Keep in mind that in order to practice good memory management it will be necessary to release this object when it is no longer required.
Identifying the Current Working Directory
As previously mentioned, when an application first loads its current working directory is the application’s root directory, represented by a / character. The current working directory may be identified at any time through a call to the currentDirectoryPath method of the file manager object. For example, the following code fragment identifies the current working directory:
NSFileManager *filemgr;
NSString *currentPath;
filemgr =[NSFileManager defaultManager];
currentPath = [filemgr currentDirectoryPath];
[filemgr release];
In this code we declare a new object named filemgr that is an instance of the NSFileManager class and also an NSString object named currentPath to contain the current working directory path. Having created a file manager instance we then call the currentDirectoryPath method of that instance and assign the resulting string to currentPath.
Identifying the Documents Directory
Each iPhone iOS application on a device has its own private Documents and tmp directories into which it is permitted to read and write data. Because the location of these directories is different for each application the only way to find the correct path is to ask the iOS for the correct path. In fact, the exact location will also differ depending on whether the application is running on a physical iPhone device, or in the iOS Simulator. The Documents directory for an application may be identified by making a call to a C function named NSSearchPathForDirectoriesInDomains, passing through an argument (in this case NSDocumentDirectory) indicating that we require the path to the Documents directory. Since this is a C function, as opposed to a method of an Objective-C class, there is no need for us to establish an instance of a Foundation class such as NSFileManager before making the call. That said, the function does return an object in the form of an NSArray containing the results of the request. We can, therefore, obtain the path to the current application’s Documents directory as follows:
NSArray *dirPaths;
NSString *docsDir;
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
When executed, the above code will assign the path to the Documents directory to the docsDir string.
When executed within the iOS Simulator environment, the path returned will take the form of:
/Users/<user name>/Library/Application Support/iPhone Simulator/<sdk version>/Applications/<app id>/Documents
Where <user name> is the name of the user currently logged into the Mac OS X system on which the simulator is running, <sdk version> is the version of the iOS SDK used to compile the application and <app id> is the unique ID of the app, for example:
06A3AEBA-8C34-476E-937F-A27BDD2E450A
Clearly this references a path on your Mac OS X system so feel free to open up a Finder window and explore the file system sandbox areas for your iOS iPhone applications.
When executed on a physical iPhone device, the path returned by the function call will take the following form:
/var/mobile/Applications/<app id>/Documents
Identifying the Temporary Directory
In addition to the Documents directory, iOS iPhone applications are also provided with a tmp directory for the storage of temporary files. The path to the current application’s temporary directory may be ascertained with a call to the
NSTemporaryDirectory C function as follows:
NSString *tmpDir = NSTemporaryDirectory();
Once executed, the string object referenced by tmpDir will contain the path to the temporary directory for the application.
Changing Directory
Having identified the path to the application’s document or temporary directory the chances are good that you will need to make that directory the current working directory. The current working directory of a running iPhone application can be changed with a call to the changeCurrentDirectoryPath method of an NSFileManager class instance. The destination directory path is passed as an argument to the instance method in the form of an NSString object. Note that this method returns a boolean YES or NO result to indicate if the requested directory change was successful or not. A failure result typically indicates that the specified directory does not exist, or that the application lacks the appropriate access permissions:
NSFileManager *filemgr;
NSArray *dirPaths;
NSString *docsDir;
filemgr =[NSFileManager defaultManager];
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
if ([filemgr changeCurrentDirectoryPath: docsDir] == NO)
{
// Directory does not exist – take appropriate action
}
[filemgr release];
In the above example, the path to the Documents directory is identified and then used as an argument to the changeCurrentDirectoryPath method of the file manager object to change the current working directory to that location.
Creating a New Directory
A new directory on an iOS device is created using the createDirectoryAtPath instance method of the NSFileManager class, once again passing through the pathname of the new directory as an argument and returning a boolean success or failure result. The second argument to this method defines whether any intermediate directory levels should be created automatically. For example, if we wanted to create a directory with the path /var/mobile/Applications/<app id>/Documents/mydata/maps and the mydata subdirectory does not yet exist, setting the withIntermediateDirectories argument to YES will cause this directory to be created automatically before then creating the maps sub-directory with in it. If this argument is set to NO, then the attempt to create the directory will fail because mydata does not already exist and we have not given permission for it to be created on our behalf.
This method also takes additional arguments in the form a set of attributes for the new directory. Specifying nil will use the default attributes.
The final argument provides the option to reference an NSError object to contain error information in event of a failure. If this is not required, NULL may be specified for this argument.
The following code fragment identifies the documents directory and creates a new sub-directory named data in that directory:
NSFileManager *filemgr;
NSArray *dirPaths;
NSString *docsDir;
NSString *newDir;
filemgr =[NSFileManager defaultManager];
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
newDir = [docsDir stringByAppendingPathComponent:@"data"];
if ([createDirectoryAtPath:newDir withIntermediateDirectories:YES attributes:nil error: NULL] == NO)
{
// Failed to create directory
}
[filemgr release];
Deleting a Directory
An existing directory may be removed from the file system using the removeItemAtPath method, passing though the path of the directory to be removed as an argument. For example, to remove the data directory created in the preceding example we might write the following code:
NSFileManager *filemgr;
NSArray *dirPaths;
NSString *docsDir;
NSString *newDir;
filemgr =[NSFileManager defaultManager];
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
newDir = [docsDir stringByAppendingPathComponent:@"data"];
if ([filemgr removeItemAtPath: newDir error: nil] == NO)
{
// Directory removal failed.
}
[filemgr release];
Listing the Contents of a Directory
A listing of the files contained within a specified directory can be obtained using the directoryContentsAtpath method. This method takes the directory pathname as an argument and returns an NSArray object containing the names of the files and sub-directories in that directory. The following example obtains a listing of the contents of the root directory (/) and displays each item in the Console window application (located in the Applications/Utilities folder of your Mac OS X system). Note that since there is no Console for the iPhone device, the output will only be visible when the application is running in the simulator environment:
NSFileManager *filemgr;
NSArray *filelist;
int count;
int i;
filemgr =[NSFileManager defaultManager];
filelist = [filemgr contentsOfDirectoryAtPath:@"/" error:NULL];
count = [filelist count];
for (i = 0; i < count; i++)
NSLog(@"%@", [filelist objectAtIndex: i]);
[filemgr release];
Getting the Attributes of a File or Directory
The attributes of a file or directory can be obtained using the attributesOfItemAtPath method. This takes as arguments the path of the directory and an optional NSError object into which information about any errors will be placed (may be specified as NULL if this information is not required). The results are returned in the form of an NSDictionary dictionary object. The keys for this dictionary are as follows:
NSFileType
NSFileTypeDirectory
NSFileTypeRegular
NSFileTypeSymbolicLink
NSFileTypeSocket
NSFileTypeCharacterSpecial
NSFileTypeBlockSpecial
NSFileTypeUnknown
NSFileSize
NSFileModificationDate
NSFileReferenceCount
NSFileDeviceIdentifier
NSFileOwnerAccountName
NSFileGroupOwnerAccountName
NSFilePosixPermissions
NSFileSystemNumber
NSFileSystemFileNumber
NSFileExtensionHidden
NSFileHFSCreatorCode
NSFileHFSTypeCode
NSFileImmutable
NSFileAppendOnly
NSFileCreationDate
NSFileOwnerAccountID
NSFileGroupOwnerAccountID
For example, we can extract the file type and POSIX permissions for the /Applications directory using the following code excerpt:
NSFileManager *filemgr;
NSDictionary *attribs;
filemgr = [NSFileManager defaultManager];
attribs = [filemgr attributesOfItemAtPath: @"/Applications" error: NULL];
NSLog (@"File type %@", [attribs objectForKey: NSFileType]);
NSLog (@"POSIX Permissions %@", [attribs objectForKey: NSFilePosixPermissions]);
[filemgr release];
When executed in the simulator, results similar to the following output will appear in the Console window:
File type NSFileTypeDirectory
POSIX Permissions 509