Working with the Contents of a Directory

This article describes two approaches to working with the contents of a directory. NSFileManager provides several methods that return arrays of paths or URLs for items in a directory. To iterate over the the contents of a directory, you can use an NSDirectoryEnumerator object.

Listing the Contents of a Directory

NSFileManager provides a several methods that return the contents of a directory as an arrays of paths or (in Mac OS X v10.6 and later) URLs.

Contents as URLs

In Mac OS X v10.6 and later, you can obtain an array of NSURL objects for each of the top-level items in a directory using contentsOfDirectoryAtURL:includingPropertiesForKeys:options:error:. (This is the URL-based equivalent of contentsOfDirectoryAtPath:error:. If you need to recurse into subdirectories, use enumeratorAtURL:includingPropertiesForKeys:options:errorHandler: as shown in Using a Directory Enumerator). If you’re only interested in the URLs and no other attributes, then pass an empty array for the keys and 0 for the options, as shown in this example:

NSURL *url = <#A URL for a directory#>;
NSError *error = nil;
NSArray *array = [[NSFileManager defaultManager]
                   contentsOfDirectoryAtURL:url
                   includingPropertiesForKeys:[NSArray array]
                   options:0
                   error:&error];
if (array == nil) {
    // Handle the error
}

One of the benefits of using URLs, however, is that you can also efficiently retrieve additional information about each item. If you want to have the property caches of the returned URLs pre-populated with a default set of attributes, then pass nil for the keys and 0 for the options. You can also specify which attributes to retrieve and options to omit subdirectories, the contents of file packages, and hidden files, as illustrated in the following example:

NSURL *url = <#A URL for a directory#>;
NSError *error = nil;
NSArray *properties = [NSArray arrayWithObjects: NSURLLocalizedNameKey,
                          NSURLCreationDateKey, NSURLLocalizedTypeDescriptionKey, nil];
 
NSArray *array = [[NSFileManager defaultManager]
                   contentsOfDirectoryAtURL:url
                   includingPropertiesForKeys:properties
                   options:(NSDirectoryEnumerationSkipsPackageDescendants |
                            NSDirectoryEnumerationSkipsHiddenFiles)
                   error:&error];
if (array == nil) {
    // Handle the error
}

Contents as Path Strings

If you simply want a list of the contents of a directory, excluding any subdirectories (and without traversing symbolic links), you use contentsOfDirectoryAtPath:error:, as shown in this example:

NSString *path = <#A path to a directory#>;
NSError *error = nil;
NSArray *array = [[NSFileManager defaultManager]
                   contentsOfDirectoryAtPath:path error:&error];
if (array == nil) {
    // Handle the error
}

If you need a recursive listing—one that contains the filenames of the items in a given directory and all its subdirectories—you can use subpathsOfDirectoryAtPath:error:, as shown in this example:

NSString *path = <#A path to a directory#>;
NSError *error = nil;
NSArray *array = [[NSFileManager defaultManager]
                   subpathsOfDirectoryAtPath:path error:&error];
if (array == nil) {
    // Handle the error
}

Using a Directory Enumerator

You use an NSDirectoryEnumerator object to enumerate the contents of a directory and any subdirectories that it contains. NSDirectoryEnumerator is an abstract class, a cover for a private concrete subclass tailored to the file system’s directory structure. You cannot create an NSDirectoryEnumerator object directly—you use NSFileManager to retrieve a suitable instance.

You use the NSFileManager method enumeratorAtPath: to retrieve a directory enumerator that returns items as file path strings. In Mac OS X v10.6 and later, you can use a directory enumerator that returns items as URLs. (If you’re going to perform any operations with the returned items, it’s typically more efficient to use URLs.) You retrieve such an instance from a file manager object using enumeratorAtURL:includingPropertiesForKeys:options:errorHandler:.

Both enumerators return the paths of all files and directories contained within that directory. The paths are relative to the directory. The enumeration is recursive, including the files of all subdirectories, and crosses device boundaries. It does not resolve symbolic links or attempt to traverse symbolic links that point to directories. Typically you simply enumerate the items in the NSDirectoryEnumerator object. You can also, though, use the skipDescendents method to avoid listing the contents of any directories you’re not interested in.

The following URL-based example (for Mac OS X v10.6 and later) illustrates how you can use enumeratorAtURL:includingPropertiesForKeys:options:errorHandler: to list all the user-visible subdirectories of a given directory, noting whether they are directories or file packages. The keys array argument specifies that for each URL produced by this enumeration the specified property values are pre-fetched and cached—this makes subsequent access more efficient. The options argument specifies that enumeration should not list the contents of file packages and hidden files. The error handler is a block object that returns a Boolean value; if it returns YES, the enumeration continues after the error; if it returns NO, the enumeration stops.

NSURL *directoryURL = <#An NSURL object that contains a reference to a directory#>;
 
NSArray *keys = [NSArray arrayWithObjects:
    NSURLIsDirectoryKey, NSURLIsPackageKey, NSURLLocalizedNameKey, nil];
 
NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager]
                                     enumeratorAtURL:directoryURL
                                     includingPropertiesForKeys:keys
                                     options:(NSDirectoryEnumerationSkipsPackageDescendants |
                                              NSDirectoryEnumerationSkipsHiddenFiles)
                                     errorHandler:^(NSURL *url, NSError *error) {
                                         // Handle the error.
                                         // Return YES if the enumeration should continue after the error.
                                         return <#YES or NO#>;
                                     }];
 
for (NSURL *url in enumerator) {
 
    // Error-checking is omitted for clarity.
 
    NSNumber *isDirectory = nil;
    [url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL];
 
    if ([isDirectory boolValue]) {
 
        NSString *localizedName = nil;
        [url getResourceValue:&localizedName forKey:NSURLLocalizedNameKey error:NULL];
 
        NSNumber *isPackage = nil;
        [url getResourceValue:&isPackage forKey:NSURLIsPackageKey error:NULL];
 
        if ([isPackage boolValue]) {
            NSLog(@"Package at %@", localizedName);
        }
        else {
            NSLog(@"Directory at %@", localizedName);
        }
    }
}

You can use other methods declared by NSDirectoryEnumerator to determine attributes of files during the enumeration—both of the parent directory and the current file or directory—and to control recursion into subdirectories. The following string-based example enumerates the contents of a directory and lists files that have been modified within the last 24 hours; if, however, it comes across RTFD file packages, it skips recursion into them:

NSString *directoryPath = <#Get a path to a directory#>;
NSDirectoryEnumerator *directoryEnumerator = [[NSFileManager defaultManager] enumeratorAtPath:directoryPath];
 
NSDate *yesterday = [NSDate dateWithTimeIntervalSinceNow:(-60*60*24)];
 
for (NSString *path in directoryEnumerator) {
 
    if ([[path pathExtension] isEqualToString:@"rtfd"]) {
        // Don't enumerate this directory.
        [directoryEnumerator skipDescendents];
    }
    else {
 
        NSDictionary *attributes = [directoryEnumerator fileAttributes];
        NSDate *lastModificationDate = [attributes objectForKey:NSFileModificationDate];
 
        if ([yesterday earlierDate:lastModificationDate] == yesterday) {
            NSLog(@"%@ was modified within the last 24 hours", path);
        }
    }
}