Enumerating the Contents of a Directory
- 枚举目录的内容
To discover the contents of a given directory, you enumerate that directory. Cocoa supports enumerating a directory one file at a time or all at once. Regardless of which option you choose, enumerate directories sparingly. Each enumeration can touch a large number of files and subdirectories. This quickly becomes expensive.
- 要发现给定目录的内容,请枚举该目录。 Cocoa支持一次枚举一个目录或一次枚举所有目录。 无论您选择哪个选项,都要谨慎枚举目录。 每个枚举都可以触及大量文件和子目录。 这很快变得昂贵。
Enumerating a Directory One File at a Time
- 一次枚举目录一个文件
Enumerating a directory one file at a time is recommended when you want to search for a specific file and stop enumerating when you find it. File-by-file enumeration uses the NSDirectoryEnumerator class, which defines the methods for retrieving items. Because NSDirectoryEnumerator
itself is an abstract class, however, you do not create instances of it directly. Instead, you use either the enumeratorAtURL:includingPropertiesForKeys:options:errorHandler: or the enumeratorAtPath: method of NSFileManager object to obtain a concrete instance of the class for use in your enumeration.
- 如果要搜索特定文件并在找到时停止枚举,建议一次枚举一个目录。 逐个文件枚举使用NSDirectoryEnumerator类,该类定义检索项的方法。 但是,因为NSDirectoryEnumerator本身是一个抽象类,所以不直接创建它的实例。 相反,您可以使用enumeratorAtURL:includesPropertiesForKeys:options:errorHandler:或NSFileManager对象的enumeratorAtPath:方法来获取要在枚举中使用的类的具体实例。
Enumerator objects return the paths of all files and directories contained within the enumerated directory. Because enumerations are recursive and cross device boundaries, the number of files and directories returned may be more than what is present in the starting directory. You can skip the contents of any directory you are not interested in by calling the enumerator object’s skipDescendents method. Enumerator objects do not resolve symbolic links or attempt to traverse symbolic links that point to directories.
- Enumerator对象返回枚举目录中包含的所有文件和目录的路径。 由于枚举是递归的并且跨设备边界,因此返回的文件和目录的数量可能比起始目录中的数量多。 您可以通过调用枚举器对象的skipDescendents方法跳过您不感兴趣的任何目录的内容。 枚举器对象不解析符号链接或尝试遍历指向目录的符号链接。
Listing 2-5 shows how to use the enumeratorAtURL:includingPropertiesForKeys:options:errorHandler:
method to list all the user-visible subdirectories of a given directory, noting whether they are directories or file packages. The keys
array tells the enumerator object to prefetch and cache information for each item. Prefetching this information improves efficiency by touching the disk only once. The options argument specifies that the 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 the block returns YES
, the enumeration continues after the error; if it returns NO
, the enumeration stops.
- 清单2-5显示了如何使用enumeratorAtURL:includesPropertiesForKeys:options:errorHandler:方法列出给定目录的所有用户可见子目录,注意它们是目录还是文件包。 keys数组告诉枚举器对象预取和缓存每个项目的信息。 通过仅触摸一次磁盘,预取此信息可提高效率。 options参数指定枚举不应列出文件包和隐藏文件的内容。 错误处理程序是一个返回布尔值的块对象。 如果块返回YES,则在错误之后继续枚举; 如果返回NO,则枚举停止。
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 code in Listing 2-6 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.
- 您可以使用NSDirectoryEnumerator声明的其他方法来确定枚举期间文件的属性 - 父目录和当前文件或目录 - 并控制递归到子目录。 清单2-6中的代码枚举了目录的内容,并列出了在过去24小时内修改过的文件。 但是,如果遇到RTFD文件包,它会跳过递归。
Listing 2-6 Looking for files that have been modified recently
寻找最近修改过的文件
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);
}
}
}
Getting the Contents of a Directory in a Single Batch Operation
- 在单个批处理操作中获取目录的内容
If you know that you want to look at every item in a directory, you can retrieve a snapshot of the items and iterate over them at your convenience. Retrieving the contents of a directory in a batch operation is not the most efficient way to enumerate a directory, because the file manager must walk the entire directory contents every time. However, if you plan to look at all the items anyway, it is a much simpler way to retrieve the items.
- 如果您知道要查看目录中的每个项目,则可以检索项目的快照并在方便时迭代它们。 在批处理操作中检索目录的内容不是枚举目录的最有效方法,因为文件管理器必须每次都遍历整个目录内容。 但是,如果您计划查看所有项目,则检索项目的方式要简单得多。
There are two options for doing a batch enumeration of a directory using NSFileManager:
使用NSFileManager对目录进行批量枚举有两种选择:
-
To perform a shallow enumeration, use the contentsOfDirectoryAtURL:includingPropertiesForKeys:options:error: or contentsOfDirectoryAtPath:error: method.
- 要执行浅枚举,请使用contentsOfDirectoryAtURL:includingPropertiesForKeys:options:error:或contentsOfDirectoryAtPath:error:方法。
-
To perform a deep enumeration and return only subdirectories, use the subpathsOfDirectoryAtPath:error: method.
- 要执行深枚举并仅返回子目录,请使用subpathsOfDirectoryAtPath:error:方法。
Listing 2-7 shows an example that uses the contentsOfDirectoryAtURL:includingPropertiesForKeys:options:error: method to enumerate the contents of a directory. One of the benefits of using URLs is that you can also efficiently retrieve additional information about each item. This example retrieves the localized name, creation date, and type information for each item in the directory and stores that information in the corresponding NSURL object. When the method returns, you can proceed to iterate over the items in the array
variable and do what you need with them.
- 清单2-7显示了一个使用contentsOfDirectoryAtURL的示例:includesPropertiesForKeys:options:error:方法枚举目录的内容。 使用URL的一个好处是您还可以有效地检索有关每个项目的其他信息。 此示例检索目录中每个项目的本地化名称,创建日期和类型信息,并将该信息存储在相应的NSURL对象中。 当方法返回时,您可以继续迭代数组变量中的项目并使用它们执行所需的操作。
Listing 2-7 Retrieving the list of items in a directory all at once
- 一次检索目录中的项目列表
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:(NSDirectoryEnumerationSkipsHiddenFiles)
error:&error];
if (array == nil) {
// Handle the error
}
Determining Where to Store Your App-Specific Files
- 确定应用程序特定文件的存储位置
The Library
directory is the designated repository for files your app creates and manages on behalf of the user. Consider that these directories may be in different locations if your app is sandboxed. As a result, always use the NSFileManager
method URLsForDirectory:inDomains: with the NSUserDomainMask as the domain to locate the specific directory that you should use to store this data.
Library目录是您的应用程序代表用户创建和管理的文件的指定存储库。 如果您的应用是沙盒,请考虑这些目录可能位于不同的位置。 因此,始终使用NSFileManager方法URLForDirectory:inDomains:使用NSUserDomainMask作为域来查找应用于存储此数据的特定目录。
-
Use the
Application Support
directory constant NSApplicationSupportDirectory , appending your <bundle_ID> for:
使用Application Support目录常量NSApplicationSupportDirectory,将<bundle_ID>附加到:-
Resource and data files that your app creates and manages for the user. You might use this directory to store app state information, computed or downloaded data, or even user created data that you manage on behalf of the user.
- 您的应用为用户创建和管理的资源和数据文件。 您可以使用此目录存储应用程序状态信息,计算或下载的数据,甚至是您代表用户管理的用户创建的数据。
-
Autosave files.
- 自动保存文件。
-
-
Use the
Caches
directory constant NSCachesDirectory, appending your <bundle_ID> directory for cached data files or any files that your app can re-create easily.- 使用Caches目录常量NSCachesDirectory,将<bundle_ID>目录附加到缓存的数据文件或应用程序可以轻松重新创建的任何文件。
-
Read and write preferences using the NSUserDefaults class. This class automatically writes preferences to the appropriate location.
- 使用NSUserDefaults类读取和写入首选项。 此类自动将首选项写入适当的位置。
-
Use the
NSFileManager
method URLsForDirectory:inDomains: to get the directory for storing temporary files. Temporary files are files you intend to use immediately for some ongoing operation but then plan to discard later. Delete temporary files as soon as you are done with them.- 使用NSFileManager方法URLForDirectory:inDomains:获取用于存储临时文件的目录。 临时文件是您打算立即用于某些正在进行的操作但稍后计划丢弃的文件。 完成后立即删除临时文件。
Important: If you do not delete temporary files after three days, the system may delete them for you whether you are using them or not.
- 如果您在三天后未删除临时文件,系统可能会删除它们,无论您是否使用它们。
Note: Apps rarely share the files stored in the Application Support
, Caches
or Temp
directories. Therefore, you don’t typically need to use a file coordinator (NSFileCoordinator
) when reading or writing to these locations. For more information about file coordination, see The Role of File Coordinators and Presenters.
- 应用很少共享存储在Application Support,Caches或Temp目录中的文件。 因此,在读取或写入这些位置时,通常不需要使用文件协调器(NSFileCoordinator)。 有关文件协调的详细信息,请参阅文件协调器和演示者的角色。
Tips for Accessing Files and Directories
- 访问文件和目录的提示
Because the file system is a resource shared by all processes, including system processes, use it carefully. Even on systems with solid-state disk drives, file operations tend to be a little slower because of the latency involved in retrieving data from the disk. And when you do access files, it is important that you do so in a way that is secure and does not interfere with other processes.
- 由于文件系统是所有进程(包括系统进程)共享的资源,因此请谨慎使用。 即使在具有固态磁盘驱动器的系统上,由于从磁盘检索数据所涉及的延迟,文件操作往往会慢一些。 当您访问文件时,以安全且不干扰其他进程的方式执行此操作非常重要。
Perform File Operations Lazily
- 懒惰地执行文件操作
Operations involving the file system should be performed only when they are absolutely necessary. Accessing the file system usually takes a lot of time relative to other computer-wide tasks. So make sure you really need to access the disk before you do. Specifically:
涉及文件系统的操作只有在绝对必要时才应执行。 访问文件系统通常需要相对于其他计算机范围的任务花费大量时间。 因此,请确保在执行之前确实需要访问磁盘。 特别:
-
Write data to disk only when you have something valuable to save. The definition of what is valuable is different for each app but should generally involve information that the user provides explicitly. For example, if your app creates some default data structures at launch time, do not save those structures to disk unless the user changes them.
- 只有在有值得保存的东西时才将数据写入磁盘。 对于每个应用程序,有价值的定义是不同的,但通常应该涉及用户明确提供的信息。 例如,如果您的应用在启动时创建了一些默认数据结构,请不要将这些结构保存到磁盘,除非用户更改它们。
-
Read data from disk only when you need it. In other words, load data that you need for your user interface now but do not load an entire file just to get one small piece of data from that file. For custom file formats, use file mapping or read only the few chunks of a file that you need to present your user interface. Read any remaining chunks as needed in response to user interactions with the data. For structured data formats, use Core Data to manage and optimize the reading of data for you.
- 只在需要时从磁盘读取数据。 换句话说,现在加载您的用户界面所需的数据,但不加载整个文件只是为了从该文件中获取一小段数据。 对于自定义文件格式,请使用文件映射或只读取显示用户界面所需的文件的几个块。 根据用户与数据的交互,根据需要读取任何剩余的块。 对于结构化数据格式,请使用Core Data为您管理和优化数据读取。
Use Secure Coding Practices
- 使用安全编码实践
There are several principles you can follow to help ensure that you do not have file-based security vulnerabilities in your program:
- 您可以遵循以下几个原则来帮助确保您的程序中没有基于文件的安全漏洞:
-
Check the result codes for any functions or methods you call. Result codes are there to let you know that there is a problem, so do pay attention to them. For example, if you try to delete a directory that you think is empty and get an error code, you might find that the directory is not empty after all.
- 检查您调用的任何函数或方法的结果代码。 结果代码可以让您知道存在问题,因此请注意它们。 例如,如果您尝试删除您认为为空的目录并获取错误代码,则可能会发现该目录毕竟不为空。
-
When working in a directory to which your process does not have exclusive access, you must check to make sure a file does not exist before you create it. You must also verify that the file you intend to read from or write to is the same file you created.
- 在进程没有独占访问权限的目录中工作时,必须先检查以确保文件在创建之前不存在。 您还必须验证要读取或写入的文件是否与您创建的文件相同。
-
Whenever possible use routines that operate on file descriptors rather than pathnames. In this way you can be sure you’re always dealing with the same file.
- 尽可能使用对文件描述符而不是路径名进行操作的例程。 通过这种方式,您可以确保始终处理相同的文件。
-
Intentionally create files as a separate step from opening them so that you can verify that you are opening a file you created rather than one that already existed
- 故意创建文件作为打开它们的单独步骤,以便您可以验证是否打开了您创建的文件而不是已存在的文件
-
Know whether the function you are calling follows symbolic links. For example, the
lstat
function gives you the status of a file regardless of whether it’s a normal file or a symbolic link, but thestat
function follows symbolic links and, if the specified file was a symbolic link, returns the status of the linked-to file. Therefore, if you use thestat
function, you might be accessing a different file than you expected.- 知道您正在呼叫的功能是否遵循符号链接。 例如,lstat函数为您提供文件的状态,无论它是普通文件还是符号链接,但stat函数遵循符号链接,如果指定的文件是符号链接,则返回链接的状态 - 提交。 因此,如果使用stat函数,则可能正在访问与预期不同的文件。
-
Before you read a file, make sure that file has the owner and permissions you expect. Be prepared to fail gracefully (rather than hanging) if it does not.
- 在读取文件之前,请确保该文件具有您期望的所有者和权限。 准备好优雅地失败(而不是悬挂),如果没有。
-
Set your process’ file code creation mask (umask) to restrict access to files created by your process. The umask is a bitmask that alters the default permissions of a new file. You do not reset the umask for your app, your process inherits the umask from its parent process. For more information about setting the umask, see the
umask(2)
man page.- 设置进程的文件代码创建掩码(umask)以限制对进程创建的文件的访问。 umask是一个位掩码,用于更改新文件的默认权限。 您没有重置应用程序的umask,您的进程从其父进程继承umask。 有关设置umask的更多信息,请参见umask(2)手册页。
For additional tips and coding practices, see Race Conditions and Secure File Operations in Secure Coding Guide.
- 有关其他提示和编码实践,请参阅安全编码指南中的竞争条件和安全文件操作。
Assume Case Sensitivity for Paths
- 假设路径的区分大小写
When searching or comparing filenames, always assume that the underlying file system is case sensitive. macOS supports many file systems that use case to differentiate between files. Even on file systems (such as APFS and HFS+) that support case insensitivity, there are still times when case may be used to compare filenames. For example, the NSBundle class and CFBundle APIs consider case when searching bundle directories for named resources.
- 搜索或比较文件名时,请始终假定基础文件系统区分大小写。 macOS支持许多使用大小写来区分文件的文件系统。 即使在支持不区分大小写的文件系统(例如APFS和HFS +)上,仍有时候可以使用大小写来比较文件名。 例如,NSBundle类和CFBundle API在搜索bundle目录中查找命名资源时会考虑这种情况。
Include Filename Extensions for New Files
- 包含新文件的文件名扩展名
All files should have a filename extension that reflects the type of content contained in the file. Filename extensions help the system determine how to open files and also make it easier to exchange files with other computers or other platforms. For example, network transfer programs often use filename extensions to determine the best way to to transfer files between computers.
- 所有文件都应具有文件扩展名,以反映文件中包含的内容类型。 文件扩展名有助于系统确定如何打开文件,还可以更轻松地与其他计算机或其他平台交换文件。 例如,网络传输程序通常使用文件扩展名来确定在计算机之间传输文件的最佳方式。
Use Display Names When Presenting Items
- 在呈现项目时使用显示名称
Whenever you need to present the name of a file or directory in your user interface, always use the item’s display name. Using the display name ensures that what your app presents matches what the Finder and other apps are presenting. For example, if your app shows the path to a file, using the display name ensures that the path is localized in accordance with the current user’s language settings.
- 每当您需要在用户界面中显示文件或目录的名称时,请始终使用项目的显示名称。 使用显示名称可确保您的应用所呈现的内容与Finder和其他应用所呈现的内容相匹配。 例如,如果您的应用显示文件的路径,则使用显示名称可确保根据当前用户的语言设置对路径进行本地化。
For more information about display names, see Files and Directories Can Have Alternate Names.
- 有关显示名称的详细信息,请参阅文件和目录可以具有备用名称。
Accessing Files Safely from Background Threads
- 从后台线程安全地访问文件
In general, the objects and functions used to access and manipulate files can be used from any thread of your app. However, as with all threaded programming, make sure you manipulate files safely from your background threads:
通常,可以从应用程序的任何线程使用用于访问和操作文件的对象和函数。 但是,与所有线程编程一样,请确保从后台线程安全地操作文件:
-
Avoid using the same file descriptor from multiple threads. Doing so could cause each thread to disrupt the state of the other.
- 避免使用来自多个线程的相同文件描述符。 这样做可能会导致每个线程中断另一个线程的状态。
-
Always use file coordinators to access files. File coordinators provide notifications when other threads in your program (or other processes) access the same file. You can use these notifications to clean up your local data structures or perform other relevant tasks.
- 始终使用文件协调器来访问文件。 当程序(或其他进程)中的其他线程访问同一文件时,文件协调器会提供通知。 您可以使用这些通知来清理本地数据结构或执行其他相关任务。
-
Create a unique instance of NSFileManager for each thread when copying, moving, linking, or deleting files. Although most file manager operations are thread-safe, operations involving a delegate require a unique instance of
NSFileManager
, especially if you are performing those operations on background threads.- 在复制,移动,链接或删除文件时,为每个线程创建唯一的NSFileManager实例。 尽管大多数文件管理器操作都是线程安全的,但涉及委托的操作需要NSFileManager的唯一实例,尤其是在后台线程上执行这些操作时。
网友评论