Managing file systems using NSFileManager

Posted by jeff_papciak on Fri, 24 Dec 2021 11:22:09 +0100

As one of the basic resources used by all processes, file system is mainly used to process data files, applications and persistent storage related to the operating system itself in Mac OS and iOS.

The file system format in iOS is HFS Plus, and the main format in macOS is HFS Plus. Since the number of files can easily reach millions, the file system uses a directory to establish a hierarchical organization. Although the basic directory structure of iOS and macOS is similar, the system is different in the way of organizing applications and user data. In this chapter, we only introduce the iOS file system.

1. Basic introduction to IOS file system

For security reasons, the iOS system places each app and its data in its own sandbox. Each app can only access the files and data in its own sandbox directory. When installing a new app, the installer will create multiple container directories for the app in the sandbox directory, and each container directory has a specific purpose. As shown in the following figure:

  • The Bundle Container directory contains application packages.
  • The Data Container directory contains app and user data, which can be further divided into several subdirectories for app to classify data.
  • Applications can also request access to directories such as iCloud containers at run time.

Generally, apps are prohibited from accessing or creating files outside their container directory. However, there are exceptions. For example, the app uses the API to access the system address book, camera, photos, etc. under the authorization of the user.

The following table lists some important subdirectories and describes their intended use. In addition, it explains other access restrictions for each subdirectory and indicates whether the contents of the directory are backed up by iTunes and iCloud.

catalogueintroduce
MyApp.appThis is the resource package for the application. A Bundle is a directory (or possibly a file) that allows related resources (such as executable code, localized resources, pictures, etc.) to be combined and treated as a separate file in some cases

You cannot write files to this directory. To prevent tampering, the bundle directory will be signed during installation. Writing to this file will change the signature, resulting in the app unable to start. However, you can read the resources in the bundle.  

iTunes and iCloud do not back up the contents of this directory.
Documents(NSDocumentDirectory)This directory is used to store user generated content. Users can access the contents of the directory through file sharing, so the directory should only contain the contents you want to disclose to users.  

iTunes and iCloud back up the contents of the directory.
Library(NSLibraryDirectory)This top-level directory is used to store non user data files. Generally, the files are placed in several standard subdirectories. iOS applications generally use the Application Support, Preferences and Caches subdirectories of this directory. You can also customize the subdirectories.  

iTunes and iCloud back up the contents of directories other than Caches.
tmp(NSTemporaryDirectory())This directory is used to store temporary files that do not need to be saved between application startup. App should actively delete these files when they are not needed. When the app is not running, the system may empty the contents of the directory.  

iTunes and iCloud do not back up the contents of this directory.

In order to better organize the hierarchy of the file system, you can create directories in Documents, Library and tmp directories.

In order to have a clearer understanding of the directory, the following code is used to obtain the directory. Create a demo of the Single View Application template. The demo name is FileManager. Add the following code to the viewDidLoad method.

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 1. Bundle directory
    NSLog(@"bundlePath %@",[NSBundle mainBundle].bundlePath);
    
    // 2. Sandbox home directory
    NSString *homeDir = NSHomeDirectory();
    NSLog(@"homeDir %@",homeDir);
    
    // 3.Documents directory
    NSLog(@"Documents url %@",[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject);
    NSLog(@"Documents pathA %@",NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject);
    NSLog(@"Documents pathB %@",[homeDir stringByAppendingPathComponent:@"Documents"]);
    
    // 4.Library Directory
    NSLog(@"Library url %@",[[NSFileManager defaultManager] URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask].firstObject);
    NSLog(@"Library pathA %@",NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).firstObject);
    NSLog(@"Library pathB %@",[homeDir stringByAppendingPathComponent:@"Library"]);
    
    // 5.Caches directory
    NSLog(@"Caches url %@",[[NSFileManager defaultManager] URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask].firstObject);
    NSLog(@"Caches path %@",NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject);
    
    // 6.tep directory
    NSLog(@"tmpA %@",NSTemporaryDirectory());
    NSLog(@"tmpB %@",[homeDir stringByAppendingPathComponent:@"tmp"]);

In the above code, some use a variety of methods to output directories. For example, some use the NSSearchPathForDirectoriesInDomains() method to obtain directories, and some are spliced after the sandbox home directory.

Run the demo, and the console output is as follows:

// 1. Bundle directory
bundlePath /Users/ad/Library/Developer/CoreSimulator/Devices/B839573B-A8AD-490A-9B62-0A75813758BA/data/Containers/Bundle/Application/2E075CE6-0AE6-4D00-AB71-95313712ACE3/FileManager&FileHandle.app

// 2. Sandbox home directory
homeDir /Users/ad/Library/Developer/CoreSimulator/Devices/B839573B-A8AD-490A-9B62-0A75813758BA/data/Containers/Data/Application/7917C8C6-C48A-4E91-83BA-F5C269E02951

// 3.Documents directory
Documents url file:///Users/ad/Library/Developer/CoreSimulator/Devices/B839573B-A8AD-490A-9B62-0A75813758BA/data/Containers/Data/Application/7917C8C6-C48A-4E91-83BA-F5C269E02951/Documents/
Documents pathA /Users/ad/Library/Developer/CoreSimulator/Devices/B839573B-A8AD-490A-9B62-0A75813758BA/data/Containers/Data/Application/7917C8C6-C48A-4E91-83BA-F5C269E02951/Documents
Documents pathB /Users/ad/Library/Developer/CoreSimulator/Devices/B839573B-A8AD-490A-9B62-0A75813758BA/data/Containers/Data/Application/7917C8C6-C48A-4E91-83BA-F5C269E02951/Documents

// 4.Library Directory
Library url file:///Users/ad/Library/Developer/CoreSimulator/Devices/B839573B-A8AD-490A-9B62-0A75813758BA/data/Containers/Data/Application/7917C8C6-C48A-4E91-83BA-F5C269E02951/Library/
Library pathA /Users/ad/Library/Developer/CoreSimulator/Devices/B839573B-A8AD-490A-9B62-0A75813758BA/data/Containers/Data/Application/7917C8C6-C48A-4E91-83BA-F5C269E02951/Library
Library pathB /Users/ad/Library/Developer/CoreSimulator/Devices/B839573B-A8AD-490A-9B62-0A75813758BA/data/Containers/Data/Application/7917C8C6-C48A-4E91-83BA-F5C269E02951/Library

// 5.Caches directory
Caches url file:///Users/ad/Library/Developer/CoreSimulator/Devices/B839573B-A8AD-490A-9B62-0A75813758BA/data/Containers/Data/Application/7917C8C6-C48A-4E91-83BA-F5C269E02951/Library/Caches/
Caches path /Users/ad/Library/Developer/CoreSimulator/Devices/B839573B-A8AD-490A-9B62-0A75813758BA/data/Containers/Data/Application/7917C8C6-C48A-4E91-83BA-F5C269E02951/Library/Caches

// 6.tep directory
tmpA /Users/ad/Library/Developer/CoreSimulator/Devices/B839573B-A8AD-490A-9B62-0A75813758BA/data/Containers/Data/Application/7917C8C6-C48A-4E91-83BA-F5C269E02951/tmp/
tmpB /Users/ad/Library/Developer/CoreSimulator/Devices/B839573B-A8AD-490A-9B62-0A75813758BA/data/Containers/Data/Application/7917C8C6-C48A-4E91-83BA-F5C269E02951/tmp

In the program, in order to avoid using hard coded path names, methods and functions should be used as much as possible to obtain the current directory path name, user home directory and temporary file directory.

2. In which directories should the files be placed?

Storing large files in apps to iTunes and iCloud will slow down the backup process. These apps will also consume a lot of backup space for users, which will cause users to delete your app or disable the backup data to iCloud function of the app. In order to reduce the time taken by synchronization and backup processes, the file storage should be placed in the right place. Therefore, the file location should be selected according to the following guidelines:

  1. Put user data into Documents /:
    User data typically includes any files you may want to expose to users, which can be created, imported, deleted, or edited by users. For drawing applications, user data includes any drawing file created by the user; For text editors, including text files created by users; For audio and video applications, including files downloaded by the user for later viewing or listening.

  2. Place the support files created by the application in the Library/Application support / Directory:
    Generally, the files in this directory are used to support app operation and should not be touched by users. The directory can also include data files, configuration files, templates, and modified versions of resources loaded from application packages.

  3. You can exclude files that do not need to be backed up through the [NSURL setResourceValue: forKey: error:] method:
    iCloud will back up the contents of Documents / and Application Support / directories by default. You can exclude files that do not need to be backed up through the [NSURL setResourceValue: forKey: error:] method. At this time, the key should be NSURLIsExcludedFromBackupKey. Any file that can be re created or downloaded must be drained from the backup, which is particularly important for large media files. If your app needs to download audio or video, be sure to exclude the downloaded audio and video from the backup.

  4. Put temporary data in tmp / Directory:
    Temporary data includes any data that does not need to be retained for a long time. Remember to delete these data when they are not needed so that they no longer occupy the space of user equipment. When the app is not running, the system will periodically clear these files. Therefore, any data that needs long-term use cannot be placed in the tmp / directory.

  5. Place the data cache file in the Library/Caches / Directory:
    The data cache file is saved longer than temporary data, but it is not persistent with Application Support. Generally speaking, programs can run normally without cached data, but with cached data, performance can be improved. Cache data includes but is not limited to database cache files, downloadable files, etc. The system may delete the contents of the Caches / directory to free up disk space, so your app must be able to recreate or download these files as needed.

3. Manage files and directories: NSFileManager

3.1 relative path and absolute path

Paths can be either relative or absolute. Relative path is the path name relative to the current directory. Absolute path is also called full path.

The absolute path starts with a slash /. On the Mac, the full path of the desktop directory is / users/USERNAME/desktop. This directory indicates four directories: / (root directory), users, USERNAME (user account name), and desktop.

In the absolute path, only the first slash / indicates the root directory. Other slashes are used to separate the list of directories in the path.

Suppose there is a github file on the desktop, and there is a pro648.0 in the file Txt file, if you want to describe pro648 Txt the path of this file can be either an absolute path or a relative path:

Absolute path: / users / username / desktop / GitHub / pro648 txt
Relative path: GitHub / pro648 Txt (the current directory is desktop)
Relative path: pro648 Txt (the current directory is github)

3.2 type of path

The preferred way to specify a file or directory is to use the NSURL class. Although the NSString class has many methods related to path creation, the NSURL class is more powerful in locating directories and files. For app s that use network resources at the same time, use the NSURL class to manage items on the local file system and network server at the same time.

For most URLs, you can use the NSURL method to splice directories and file names together to build URLs. During splicing, the URLs are spliced from the root directory until the project can be found. The URLs built in this way are called path based URLs. You can also connect the path and file name together to build a string based path, which is slightly different from the path based URL format. You can also create a file reference URL that uses a unique ID to identify the file or directory location.

All of the following directories are for GitHub under the Documents directory COM / pro648 path reference.

Path based URL: file:///Users/ad/Library/Developer/CoreSimulator/Devices/B839573B-A8AD-490A-9B62-0A75813758BA/data/Containers/Data/Application/82635C86-0E64-46CA-8E97-D434651551C2/Documents/github.com/pro648/

File Reference URL: file:///.file/id=16777220.17054390/

String based path:/Users/ad/Library/Developer/CoreSimulator/Devices/B839573B-A8AD-490A-9B62-0A75813758BA/data/Containers/Data/Application/82635C86-0E64-46CA-8E97-D434651551C2/Documents/github.com/pro648

Use the NSURL method to create a URL object and convert it to a File Reference URL if necessary. The NSFileManager class generally takes precedence over path based URLs. The advantage of the File Reference URL is that if the file is moved on the same disk during app operation, the unique ID of the File Reference URL will not change, while the path of the other two methods will become invalid after the file is moved, and the path must be updated.

Although it is safe to use the File Reference URL when the app is running, you cannot save the File Reference URL for reuse because the File Reference URL may change when the system restarts. If you want to save it for use after the next startup of the app, you can use the bookmarkDataWithOptions: includingResourceValuesForKeys: relativeToURL: error: method to create a bookmark.

3.3 path conversion

Use the path attribute to convert an NSURL type path to an NSString type path. You can also convert an NSString type path to an NSURL type path using the fileURLWithPath: method of the NSURL.

Continue to add the following code at the bottom of viewDidLoad.

- (void)viewDidLoad {
    ...
    // Create file manager
    NSFileManager *sharedFM = [NSFileManager defaultManager];
    
    // Create NSURL type documentsURL path, convert to NSString type and fileReferenceURL type path
    NSURL *documentsURL = [sharedFM URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject;
    NSLog(@"documentsURL:%@",documentsURL);
    NSLog(@"documentsURL convert to path:%@",documentsURL.path);
    NSLog(@"fileReferenceURL:%@",[documentsURL fileReferenceURL]);
    
    // Create NSString type libraryPath path, convert to NSURL type path
    NSString *libraryPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).firstObject;
    NSLog(@"libraryPath:%@",libraryPath);
    NSLog(@"libraryPath convert to url:%@",[NSURL fileURLWithPath:libraryPath]);
}

After operation, the console input is as follows:

// documentsURL
documentsURL:file:///Users/ad/Library/Developer/CoreSimulator/Devices/B839573B-A8AD-490A-9B62-0A75813758BA/data/Containers/Data/Application/F5811251-323E-4D61-A09E-833C8F8A2CA1/Documents/
documentsURL convert to path:/Users/ad/Library/Developer/CoreSimulator/Devices/B839573B-A8AD-490A-9B62-0A75813758BA/data/Containers/Data/Application/F5811251-323E-4D61-A09E-833C8F8A2CA1/Documents
fileReferenceURL:file:///.file/id=16777220.17053305/

// libraryPath
libraryPath:/Users/ad/Library/Developer/CoreSimulator/Devices/B839573B-A8AD-490A-9B62-0A75813758BA/data/Containers/Data/Application/F5811251-323E-4D61-A09E-833C8F8A2CA1/Library
libraryPath convert to url:file:///Users/ad/Library/Developer/CoreSimulator/Devices/B839573B-A8AD-490A-9B62-0A75813758BA/data/Containers/Data/Application/F5811251-323E-4D61-A09E-833C8F8A2CA1/Library/

3.4 perform basic operations on files or directories

The Foundation framework allows you to perform basic operations on files or directories using the file system. These basic operations are provided by the NSFileManager class. The methods of this class have the following functions:

  • Create a new file.
  • Read data from an existing file.
  • Writes data to a file.
  • Rename the file.
  • Delete file.
  • Test whether the file exists.
  • Determine the size and other properties of the file.
  • Copy files.
  • Test whether the contents of the two files are the same.

Most of the above methods can also operate on directories. For example, create a directory, read the contents of a directory, delete a directory, and so on.

3.4. 1 create directory

When you need to locate a file in a standard directory, first locate the directory using the system framework, and then build the file path using the newly generated URL.

The Foundation framework includes several methods for navigating to a standard catalog:

  • The urlsfordirectory: indomain: method of NSFileManager class returns a path of NSURL type. The first parameter of the method is the NSSearchPathDirectory constant, which can provide the URL s of most standard directories such as home directory, desktop directory and Library Directory.
  • The NSSearchPathForDirectoriesInDomains() function is similar to the URLsForDirectory: inDomains: method, except that the returned path is of NSString type.
  • The NSHomeDirectory() function returns the home directory of the user or app. When the app is in the sandbox, the home directory points to the sandbox of the application; Otherwise, the home directory points to the home directory of the user's file system. In addition, there are other similar methods to view the article start Part of the sample code.

The following code uses the method of NSFileManager class to create a custom directory in the Library/Application Support directory. Because this method will contact the file system every time, it will be very time-consuming. If you need to use this directory multiple times, you should save the returned URL instead of calling this method every time you need it.

- (NSURL *)applicationDirectory {
    // 1. Create a file manager
    NSFileManager *sharedFM = [NSFileManager defaultManager];
    
    // 2. Find the Application Support directory in the home directory path
    NSArray *possibleURLs = [sharedFM URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask];
    
    NSURL *appSupportDir = nil;
    NSURL *dirPath = nil;
    if (possibleURLs.count > 0) {
        // 3. When the array is not empty, the first element is used.
        appSupportDir = possibleURLs.firstObject;
    }
    
    // 4. If the appSupportDir directory exists, add the applied bundleIdentifier to the end of the file to create a custom directory
    if (appSupportDir) {
        NSString *appBundleID = [[NSBundle mainBundle] bundleIdentifier];
        dirPath = [appSupportDir URLByAppendingPathComponent:appBundleID];
    }
    
    // 5. If the dirPath directory does not exist, create it
    NSError *error = nil;
    if (![sharedFM createDirectoryAtURL:dirPath withIntermediateDirectories:YES attributes:nil error:&error]) {
        NSLog(@"Couldn't create dirPath.error %@",error);
        return nil;
    }
    return dirPath;
}

The step-by-step description of the above code is as follows:

  1. The file manager created here is a shared file manager, and the proxy method cannot be used. If you need to use the proxy method, use the init method to create and comply with the NSFileManagerDelegate protocol.
  2. Find the path of the Application Support directory in the main directory. The indomain: parameter here can specify multiple values. At this time, the possibleURLs array will contain multiple values, and the order of elements in the array is consistent with that of the indomain: parameter. When writing programs for iOS, the indomain: parameter should be NSUserDomainMask. This parameter can also be NSLocalDomainMask, NSNetworkingDomainMask, NSSystemDomainMask, or nsalldomainmask.
  3. When the array is not empty, the first element is used.
  4. Add bundleIdentifier to appSupportDir to create a custom directory.
  5. If the dirPath directory does not exist, it is created. createDirectoryAtURL: withIntermediateDirectories: attributes: error: the method is used to create a directory, and the createIntermediates: parameter is used to specify whether to create a parent directory if the parent directory does not exist during directory creation. When the parameter is YES, the nonexistent parent directory is automatically created; When the parameter is NO, this method will fail if any intermediate parent directory does not exist. Attributes: used to specify the attributes of the new directory file, including owner, creation date, group, etc. If nil is specified, the default attribute is used. If the directory is created successfully, the method returns YES; If the intermediate directory is created successfully and the target directory already exists, return YES; Returns NO if an error is encountered.

Call the above method at the bottom of the viewDidLoad method.

- (void)viewDidLoad {
    ...
    // Create directory
    NSURL *bundleIDDir = [self applicationDirectory];
}

3.4. 2 copy, move and delete files and directories

  • Use the copyItemAtURL: toURL: error: and copyItemAtPath: toPath: error: methods of NSFileManager to copy files or directories.
  • Move a file or directory using the moveItemAtURL: toURL: error: and moveItemAtPath: toPath: error: methods.
  • Remove a file or directory using the removeItemAtURL: error: and removeItemAtPath: error: methods.

When you copy, move, or delete a directory, the directory and all its contents are affected. Copy, move and delete here are consistent with the usage in Finder. Moving files on the same volume does not produce a new version of the file. Moving files between volumes has the same effect as copying. When copying, moving or deleting files or directories, the current process must have the permission to perform corresponding operations.

Move and copy operations can take a long time, and the NSFileManager class performs these operations synchronously. Therefore, it is recommended that any such operations be performed on the concurrent scheduling queue, not on the main thread.

The following code shows how to copy files asynchronously. The files to be copied are located in the Library/Application Support/bundleID/Data directory, the bundleID is the real bundleIdentifier of the app, and the copied files are backup files, which are located in Library/Application Support/bundleID/Data Backup directory. If the first copy operation fails, this method checks whether a backup file already exists. If present, delete the backup file. Finally, try the replication again and report an error if it fails again.

First, update viewDidLoad and pass the path created by the applicationDirectory method as a parameter to the newly created backupMyApplicationDataWithURL: method.

- (void)viewDidLoad {
    ...
    // Create directory
    NSURL *bundleIDDir = [self applicationDirectory];
    
    // duplicate catalog
    [self backupMyApplicationDataWithURL:bundleIDDir];
}

backupMyApplicationDataWithURL: the method is implemented as follows:

- (void)backupMyApplicationDataWithURL:(NSURL *)bundleIDDir {
    NSFileManager *sharedFM = [NSFileManager defaultManager];
    
    // 1. Obtain the source file and backup file path. If the source file does not exist, the source file is created.
    NSURL *appDataDir = [bundleIDDir URLByAppendingPathComponent:@"Data"];
    NSURL *backupDir = [appDataDir URLByAppendingPathExtension:@"backup"];
    if (![sharedFM fileExistsAtPath:appDataDir.path]) {
        if (![sharedFM createDirectoryAtURL:appDataDir withIntermediateDirectories:YES attributes:nil error:nil]) {
            NSLog(@"Couldn't create appDataDir");
            return;
        }
    }
    
    // 2. Perform replication asynchronously
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 3. Use the init method to initialize the file manager so that the proxy method may be used later.
        NSFileManager *theFM = [[NSFileManager alloc] init];
        NSError *anError;
        
        // 4. Try to copy the file
        if (![theFM copyItemAtURL:appDataDir toURL:backupDir error:&anError]) {
            // 5. If the copy fails, backupDir may already exist. Delete the old backupDir file
            if ([theFM removeItemAtURL:backupDir error:&anError]) {
                // 6. Copy again. If it fails, terminate the copy operation.
                if (![theFM copyItemAtURL:appDataDir toURL:backupDir error:&anError]) {
                    NSLog(@"anError:%@",anError);
                }
            }
        }
    });
}

This part of the code is very simple. Just refer to note 3 above. Instead of using the shared file manager, the file manager initialized with init can be associated with the agent method to receive the status notification of the operation file, which is convenient for customizing the file later. Remember to follow the NSFileManagerDelegate protocol.

3.4. 3 enumeration directory

You can obtain the contents of the directory by enumerating the directory. When enumerating directories, Cocoa supports enumerating one file at a time or the entire directory at a time. No matter what enumeration method is selected, enumeration will touch a large number of files and subdirectories, which will consume memory and time, so be careful when enumerating directories.

Enumerate one file at a time:

If you find a specific file, you can stop enumerating. At this time, it is recommended to enumerate one file at a time. File by file enumeration uses NSDirectoryEnumerator class, which defines the method of retrieving items. NSDirectoryEnumerator class is an abstract class, so its instance is not created directly. Use the enumerationaturl: includepropertiesforkeys: options: errorhandler: or enumerationatpath: methods of the NSFileManager class to get an instance for enumeration.

The following code shows how to use the enumeraturl: includepropertiesforkeys: options: errorHandler: method to list all visible subdirectories in the bundleIDDir directory. The keys array tells the enumerator to prefetch and cache file attribute information for each item. When enumerating, you need to contact the disk. At this time, cache this kind of information. When you need this kind of information again, you don't need to contact the disk, which can improve efficiency. Options: the parameter specifies that the enumeration should not list the contents of packages and hidden files. errorHandler: is a block that returns BOOL value. When an error occurs, if the block returns YES, the enumeration will continue; When an error occurs, the enumeration stops if the block returns NO.

- (void)enumerateOneFileAtATimeAtURL:(NSURL *)enumerateURL {
    NSArray *keys = [NSArray arrayWithObjects:NSURLIsDirectoryKey, NSURLLocalizedNameKey, nil];
    NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtURL:enumerateURL
                                                             includingPropertiesForKeys:keys
                                                                                options:NSDirectoryEnumerationSkipsSubdirectoryDescendants | NSDirectoryEnumerationSkipsHiddenFiles
                                                                           errorHandler:^BOOL(NSURL * _Nonnull url, NSError * _Nonnull error) {
                                                                               // 1. Output an error when an error is encountered and continue recursion.
                                                                               if (error) {
                                                                                   NSLog(@"[error] %@ %@",error, url);
                                                                               }
                                                                               return YES;
                                                                           }];
    for (NSURL *url in enumerator) {
        NSNumber *isDirectory = nil;
        NSError *error;
        if (![url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:&error]) {
            NSLog(@"%@ %@",error, url);
        }
        
        if (isDirectory.boolValue) {
//            // 2. Skip recursion when the extension is backup.
//            if ([url.pathExtension isEqualToString:@"backup"]) {
//                [enumerator skipDescendants];
//                continue;
//            }
            
            NSString *localizedName = nil;
            if ([url getResourceValue:&localizedName forKey:NSURLLocalizedNameKey error:NULL]) {
                NSLog(@"Directory at %@",localizedName);
            }
        }
    }
}

Because the copy directory process in the backupMyApplicationDataWithURL: method is not in the main process, this method uses the directory copied in the backupMyApplicationDataWithURL: method. In order to avoid the situation that the enumerateonefileatatimeurl: method has been executed and the copy operation of backupMyApplicationDataWithURL: method has not been completed, We call performSelector: withObject: afterDelay: in the viewDidLoad method. The code is as follows:

- (void)viewDidLoad {
    ...
    // Create directory
    NSURL *bundleIDDir = [self applicationDirectory];
    
    // duplicate catalog
    [self backupMyApplicationDataWithURL:bundleIDDir];
    
    // Enumerate one object at a time
    [self performSelector:@selector(enumerateOneFileAtATimeAtURL:) withObject:bundleIDDir afterDelay:0.3];
}

Run the demo, and the console output is as follows:

Directory at Data
Directory at Data.backup

You can also use the skip descendants method declared in NSDirectoryEnumerator to exclude directories you don't want to enumerate. cancel enumerateOneFileAtATimeAtURL: Method, run the app again, and the console output is as follows:

Directory at Data

Skipdecedents and skipdecedents have the same effect.

Enumerate the entire directory at once:

If you need to view each item in the directory, you can take a snapshot of the item and iterate over it as appropriate.

There are two options for batch enumeration of directories using NSFileManager:

  • If you only enumerate the directory and do not drill down into subdirectories and packages, use the contentsOfDirectoryAtURL: includingPropertiesForKeys: options: error: or contentsOfDirectoryAtPath: error: methods.
  • If you want to drill down to subdirectories and return only subdirectories, use the subPathsOfDirectoryAtPath: error: method.

The following code enumerates bundleIDDir directories using the contentsOfDirectoryAtURL: includingPropertiesForKeys: options: error: method. The advantage of using the URL method is that the file properties of the retrieved object can be obtained at the same time. The includingPropertiesForKeys: parameter of this method is an array, which is used to identify the file properties to prefetch for each item in the directory. If you do not want to prefetch file attributes, pass in an empty array; If you want to prefetch the default file attributes, pass in the nil attribute. Options: the parameter identifies the enumeration option because this method only performs shallow enumeration. So the supported option is NSDirectoryEnumerationSkipsHiddenFiles. This example prefetches the localized name, creation date, and type information of the project and stores this information in the corresponding NSURL object. Finally, iterate over the returned array, extract the item creation date attribute, and perform corresponding operations.

- (void)enumerateAllAtOnceAtURL:(NSURL *)enumerateURL {
    NSError *error;
    NSArray *properties = [NSArray arrayWithObjects:NSURLLocalizedNameKey, NSURLCreationDateKey, NSURLLocalizedTypeDescriptionKey, nil];
    NSArray *contents = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:enumerateURL
                                                      includingPropertiesForKeys:properties
                                                                         options:NSDirectoryEnumerationSkipsHiddenFiles
                                                                           error:&error];
    if (error) {
        NSLog(@"Couldn't enumerate directory. error:%@",error);
    }
    
    for (NSURL *url in contents) {
        NSDate *yesterday = [NSDate dateWithTimeIntervalSinceNow:(-24*60*60)];
        
        // 1. Get the file attribute of the project and use the NSURLCreationDateKey key to extract the file creation date
        NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:url.path error:nil];
        NSDate *lastModificationDate = [attributes valueForKey:NSURLCreationDateKey];
        
        // 2. If the file creates an output file path to the console within 24 hours.
        if ([yesterday earlierDate:lastModificationDate] == yesterday) {
            NSLog(@"%@ was modified within the last 24 hours", url);
        }
    }
}

Finally, call the method at the bottom of viewDidLoad using the performSelector: withObject: afterDelay: method.

- (void)viewDidLoad {
    ...
    // Create directory
    NSURL *bundleIDDir = [self applicationDirectory];
    
    // duplicate catalog
    [self backupMyApplicationDataWithURL:bundleIDDir];
    
    // Enumerate one object at a time
    [self performSelector:@selector(enumerateOneFileAtATimeAtURL:) withObject:bundleIDDir afterDelay:0.3];
    
    // Enumerate the entire directory at once
    [self performSelector:@selector(enumerateAllAtOnceAtURL:) withObject:bundleIDDir afterDelay:0.3];
}

4. About performance

If your app uses a large number of files, the code performance for related file operations is very important. Accessing files on disk is one of the slowest operations compared to other types of operations. Depending on the size and number of files, it may take milliseconds to minutes to read files from disk. Therefore, you should ensure that the code is as efficient as possible.

  • If your code reads a large number of files from disk, carefully check whether you need these files. If some are not needed immediately, you can read them later. If the same file is read and written multiple times, and the data read and written each time is very small, consider whether to combine these reads and writes into one read and write. For the same amount of data, a large read-write operation is usually more efficient than multiple small read-write operations.
  • When selecting the operation method, select the method based on the NSURL object as much as possible. Most of the NSURL class methods are designed to use technologies such as Grand Central Dispatch from the beginning, and can achieve great performance improvement without much work. The NSString class method cannot.
  • The method with block is preferred. In practice, a lot of work can be done with a small amount of code in the block, because it does not require you to define and manage the context data structure used to transmit data. You can also schedule blocks in the GCD queue, which can further improve performance. If you don't know anything about blocks, check out another of my articles: Usage of Block.
  • Reuse path objects. If you have created a path for the file, when you use the path later, you will reuse the path you created earlier and should not create it again. Reusing these paths saves time and minimizes application interaction with the file system.

Demo Name: FileManager
Source address: https://github.com/pro648/BasicDemos-iOS

reference material:

  1. File System Programming Guide
  2. NSFile​Manager

If this article is helpful to you, please click Star in the upper right corner.

If you want to get the latest articles, please click Watch in the upper right corner.

On the right, you can view the list of my articles. If you find an error, you are welcome to raise it Issue.

 

Topics: iOS