2.3 Working with Files

The Foundation framework provides access to data stored in files in several ways. All of the basic data classes have methods for initializing objects from the contents of files, and for writing the data represented by the object to a file. In addition to these convenience facilities, Foundation provides two classes that provide a much higher level of interaction with files and the filesystem: NSFileManager and NSFileHandle.

2.3.1 The File Manager

The NSFileManager class is an interface that applications use to access and manipulate files and directories in the filesystem; instances of NSFileManager provide a doorway to the filesystem for application developers. Several of NSFileManager's methods call for a handler: argument. The handler is an object that should implement fileManager:willProcessPath: and fileManager:shouldProceedAfterError: methods. These callback methods allow for error handling and confidence testing with respect to the operation being performed. In Example 2-26, nil is passed to handler: for the sake of clarity.

Additionally, methods that deal with movement around in the filesystem and perform operations on files and directories typically return a BOOL value, to indicate an operation's success or failure. Finally, methods that create new files or directories usually take a dictionary with file attributes as an argument. The attributes dictionary may take values to set the file's owner, group owner, modification date, POSIX permissions; determine whether the extension is hidden; and finally, set the HFS type and creator codes. Any unspecified attribute will take on the default value. Example 2-26 shows how to work with file managers and the filesystem.

Example 2-26. Working with NSFileManager
// Return the default manager for the filesystem
NSFileManager *fm = [NSFileManager defaultManager];

// Change the current directory; returns YES if successful
BOOL b = [fm changeCurrentDirectoryPath:@"/usr"];

// Return the path to the current directory; returns "/usr"
NSString *p = [fm currentDirectoryPath];

// Create a new directory at the path with default attributes
b = [fm createDirectoryAtPath:@"/usr/newDir" attributes:nil];

// Working with file attributes using a mutable dictionary
NSMutableDictionary *attr = [NSMutableDictionary dictionary];
[attr setObject:@"mike" forKey:NSFileOwnerAccountName];
[attr setObject:@"admin" forKey:NSFileGroupOwnerAccountName];
[attr setObject:660 forKey:NSFilePosixPermissions];

// Create a new file with these attributes
b = [fm createFileAtPath:@"/usr/newDir/newFile"
                         contents:data attributes:attr];

// Carry out practical filesystem tasks
NSString *p1 = @"/Users/mike/file"
NSString *p2 = @"/Users/mike/Documents/file"
b = [fm movePath:p1 toPath:p2 handler:nil];
b = [fm copyPath:p2 toPath:p1 handler:nil];
b = [fm removeFileAtPath:p2 handler:nil];

// Determine whether a file exists
b = [fm fileExistsAtPath:p1];

// Determine whether a file exists and if it is a directory
// Returns YES; sets dir = NO
BOOL dir;
b = [fm fileExistsAtPath:p1 isDirectory:&dir];

// Check whether the current user can read file at path
b = [fm isReadableAtPath:p1];

// Check whether the current user can write to file at path
b = [fm isWritableAtPath:p1];

// Check whether the current user can execute file at path
b = [fm isExecutableAtPath:p1];

// Check whether the current user can delete file at path
b = [fm isDeletableAtPath:p1];

// Discover the contents of directory at path
NSArray *contents = [fm directoryContentsAtPath:@"/"];

// Determine the directories contained within a directory
NSArray *subpaths = [fm subpathsAtPath:@"/"];

// An enumerator that enumerates the contents at a given path
NSEnumerator *de = [fm enumeratorAtPath:@"/"];

Filesystem paths are represented as NSString objects. Any path that you pass to an NSFileManager method must be an absolute path. This means that files in a user's home directory must be referenced as /Users/username/file, rather than ~/file. To aid in path manipulation and standardization, NSString provides a number of methods specific to these tasks, many of which are demonstrated in Example 2-27.

Example 2-27. Path manipulation with NSString
// The starting path
NSString *p1 = @"~/Documents/class.m";

// Expand the tilde; returns "/Users/mike/Documents/class.m"
p1 = [p1 stringByExpandingTildeInPath];

// Get the last path component; returns "class.m"
p1 = [p1 lastPathComponent];

// This is how you determine the path extension; returns "m"
p1 = [p1 pathExtension];

// Delete the extension; returns "class"
p1 = [p1 stringByDeletingPathExtension];

// Add a path component to a path; returns "/Users/mike/class"
p1 = [@"/Users/mike" stringByAppendingPathComponent:p1];

// Place the tilde back in; returns "~/class"
p1 = [p1 stringByAbbreviatingWithTildeInPath];

// Add an extension; returns "~/class.h"
p1 = [p1 stringByAppendingPathExtension:@"h"];

The Foundation framework also provides several functions that return paths to common locations in the filesystem. NSHomeDirectory returns the path to the home directory of the currently logged-in user. NSHomeDirectoryForUser takes a username as an argument and returns the home directory for that user. The function NSTemporaryDirectory returns a string that is the path to the current temporary directory, typically /tmp.

2.3.2 File Handles

NSFileHandle lets developers access and manipulate file data with a fine degree of control by providing methods for moving a pointer within a file, as well as inserting, deleting, and extracting data from the file. Moreover, in the true spirit of Unix, NSFileHandle can represent a gateway to communication channels such as pipes, sockets, and devices (such as /dev/null, /dev/stderr, /dev/console). NSFileHandle is covered further in Chapter 6, where methods for asynchronous reading and writing are discussed. Example 2-28 explores some uses of NSFileHandle.

Example 2-28. Working with NSFileHandle
// Create a file handle for reading an arbitrary file
NSFileHandle *fh = [NSFileHandle fileHandleForReadingAtPath:p1];
fh = [NSFileHandle fileHandleForReadingAtPath:@"/dev/srandom"];

// Create a file handle for writing to a file
fh = [NSFileHandle fileHandleForWritingAtPath:p2];
fh = [NSFileHandle fileHandleForWritingAtPath:@"/dev/null"];

// Create commonly used file handles
fh = [NSFileHandle fileHandleWithStandardError];
fh = [NSFileHandle fileHandleWithStandardInput];
fh = [NSFileHanfle fileHandleWithStandardOutput];
fh = [NSFileHandle fileHandleWithNullDevice];

// Write data to a file handle
NSString *str = @"Other Unix boxes";
NSData *data = [str dataUsingEncoding:NSASCIIStringEncoding];
[fh writeData:data];

// Read data from a file handle;
// this could be used for something like % echo "Hello" | ThisApp
fh = [NSFileHandle fileHandleWithStandardInput];

// Read all available data and converting it to a string
NSData *data = [fh availableData];
NSString *str = [NSString stringWithData:data];

// You can also read a specified number of bytes
data = [fh readDataOfLength:400];

// Or you can read up to an end-of-file
data = [fh readDataToEndOfFile];

// And close it when finished
[fh closeFile];

    Part II: API Quick Reference