Reading and writing files involves transferring a sequence of bytes between your code and the underlying disk. This is the lowest-level form of file management but is also the foundation for more sophisticated techniques as well. At some point, even the most sophisticated data structures have to be turned into a sequence of bytes before they can be stored on disk. Similarly, that same data must be read from the disk as a sequence of bytes before it can be used to reconstruct the more sophisticated data structures that it represents.
- 读取和写入文件涉及在代码和底层磁盘之间传输一系列字节。 这是文件管理的最低级别形式,但也是更复杂技术的基础。 在某些时候,即使是最复杂的数据结构也必须在它们存储在磁盘上之前变成一个字节序列。 类似地,必须从磁盘读取相同的数据作为字节序列,然后才能用于重建它所代表的更复杂的数据结构。
There are several different technologies for reading and writing the contents of files, nearly all of which are supported by both iOS and macOS. All of them do essentially the same thing but in slightly different ways. Some technologies require you to read and write file data sequentially, while others may allow you to jump around and operate on only part of a file. Some technologies provide automatic support for reading and writing asynchronously, while others execute synchronously so that you have more control over their execution.
- 有几种不同的技术可用于读取和写入文件内容,几乎所有这些技术都受iOS和macOS的支持。 所有这些都基本上做同样的事情,但方式略有不同。 某些技术要求您按顺序读取和写入文件数据,而其他技术可能允许您跳转并仅对文件的一部分进行操作。 某些技术为异步读写提供自动支持,而其他技术则同步执行,以便您可以更好地控制其执行。
Choosing from the available technologies is a matter of deciding how much control you want over the reading and writing process and how much effort you want to spend writing your file management code. Higher-level technologies like Cocoa streams limit your flexibility but provide an easy-to-use interface. Lower-level technologies like POSIX and Grand Central Dispatch (GCD) give you maximum flexibility and power but require you to write a little more code.
- 从可用技术中进行选择的问题在于决定您对读写过程的控制程度以及您希望花费多少精力编写文件管理代码。 像Cocoa流这样的高级技术限制了您的灵活性,但提供了易于使用的界面。 POSIX和Grand Central Dispatch(GCD)等低级技术为您提供了最大的灵活性和强大功能,但需要您编写更多代码。
Reading and Writing Files Asynchronously
Because file operations involve accessing a disk (possibly one on a network server), performing those operations asynchronously is almost always preferred. Technologies such as Cocoa streams and Grand Central Dispatch (GCD) are designed to execute asynchronously at all times, which allows you to focus on reading and writing file data rather than worrying about where your code executes.
Processing an Entire File Linearly Using Streams
If you always read or write a file’s contents from start to finish, streams provide a simple interface for doing so asynchronously. Streams are typically used for managing sockets and other types of sources where data may become available over time. However, you can also use streams to read or write an entire file in one or more bursts. There are two types of streams available:
如果您始终从头到尾读取或写入文件的内容,则流提供了一个简单的接口,可以异步执行此操作。 流通常用于管理套接字和其他类型的源,其中数据可能随着时间的推移而变得可用。 但是,您也可以使用流以一个或多个突发方式读取或写入整个文件。 有两种类型的流可用:
-
Use an NSOutputStream to write data sequentially to disk.
- 使用NSOutputStream将数据顺序写入磁盘。
-
Use an NSInputStream object to read data sequentially from disk.
- 使用NSInputStream对象从磁盘顺序读取数据。
Stream objects use the run loop of the current thread to schedule read and write operations on the stream. An input stream wakes up the run loop and notifies its delegate when there is data waiting to be read. An output stream wakes up the run loop and notifies its delegate when there is space available for writing data. When operating on files, this behavior usually means that the run loop is woken up several times in quick succession so that your delegate code can read or write the file data. It also means that your delegate code is called repeatedly until you close the stream object, or in the case of input streams until the stream reaches the end of the file.
- Stream对象使用当前线程的运行循环来调度流上的读写操作。 输入流唤醒运行循环,并在有数据等待读取时通知其委托。 输出流唤醒运行循环,并在有可用空间写入数据时通知其委托。 对文件进行操作时,这种行为通常意味着快速连续几次唤醒运行循环,以便代理代码可以读取或写入文件数据。 它还意味着重复调用代理代码,直到关闭流对象,或者在输入流的情况下,直到流到达文件末尾。
For information and examples about how to set up and use stream objects to read and write data, see Stream Programming Guide.
- 有关如何设置和使用流对象以读取和写入数据的信息和示例,请参阅“流编程指南”。
Processing a File Using GCD
Grand Central Dispatch provides several different ways to read or write the content of files asynchronously:
Grand Central Dispatch提供了几种不同的方式来异步读取或写入文件内容:
-
Create a dispatch I/O channel and use it to read or write data.
- 创建调度I / O通道并使用它来读取或写入数据。
-
Use the dispatch_read or dispatch_write convenience functions to perform a single asynchronous operation.
- 使用dispatch_read或dispatch_write便捷函数执行单个异步操作。
-
Create a dispatch source to schedule the execution of a custom event handler block, in which you use standard POSIX calls to read or write data from the file.
- 创建调度源以计划自定义事件处理程序块的执行,在该块中使用标准POSIX调用来读取或写入文件中的数据。
Dispatch I/O channels are the preferred way to read and write files because they give you direct control over when file operations occur but still allow you to process the data asynchronously on a dispatch queue. A dispatch I/O channel is an dispatch_io_t structure that identifies the file whose contents you want to read or write. Channels can be configured for stream-based access or random access of files. A stream-based channel forces you to read or write file data sequentially, whereas a random-access channel lets you read or write at any offset from the beginning of the file.
- 调度I / O通道是读取和写入文件的首选方式,因为它们可以直接控制文件操作何时发生,但仍允许您在调度队列上异步处理数据。 调度I / O通道是dispatch_io_t结构,用于标识要读取或写入其内容的文件。 可以为通道配置基于流的访问或文件的随机访问。 基于流的通道强制您按顺序读取或写入文件数据,而随机访问通道允许您在文件开头的任何偏移处进行读取或写入。
If you do not want the trouble of creating and managing a dispatch I/O channel, you can use the dispatch_read or dispatch_write functions to perform a single read or write operation on a file descriptor. These methods are convenient for situations where you do not want or need the overhead of creating and managing a dispatch I/O channel. However, you should use them only when performing a single read or write operation on a file. If you need to perform multiple operations on the same file, creating a dispatch I/O channel is much more efficient.
- 如果您不希望创建和管理调度I / O通道,则可以使用dispatch_read或dispatch_write函数对文件描述符执行单个读取或写入操作。 这些方法适用于您不希望或需要创建和管理调度I / O通道的开销的情况。 但是,只有在对文件执行单个读取或写入操作时才应使用它们。 如果需要对同一文件执行多个操作,则创建调度I / O通道会更有效。
Dispatch sources allow you to process files in a way that is similar to Cocoa stream objects. Like stream objects, they are used more often with sockets or data sources that send and receive data sporadically but they can still be used with files. A dispatch source schedules its associated event handler block whenever there is data waiting to be read or space available for writing. For files, this usually results in the block being scheduled repeatedly and in quick succession until you explicitly cancel the dispatch source or it reaches the end of the file it is reading.
- Dispatch源允许您以与Cocoa流对象类似的方式处理文件。 与流对象一样,它们更常用于偶尔发送和接收数据的套接字或数据源,但它们仍然可以与文件一起使用。 只要有数据等待读取或空间可用于写入,调度源就会调度其关联的事件处理程序块。 对于文件,这通常会导致块被重复安排并快速连续,直到您明确取消调度源或它到达正在读取的文件的末尾。
For more information about creating and using dispatch sources, see Concurrency Programming Guide. For information about the dispatch_read
or dispatch_write
functions, or any other GCD functions, see Grand Central Dispatch (GCD) Reference.
- 有关创建和使用调度源的更多信息,请参阅“并发编程指南”。 有关dispatch_read或dispatch_writefunctions或任何其他GCD函数的信息,请参阅Grand Central Dispatch(GCD)参考。
Creating and Using a Dispatch I/O Channel
- 创建和使用调度I / O通道
To create a dispatch I/O channel, you must provide either a file descriptor or the name of the file you want to open. If you already have an open file descriptor, passing it to the channel changes the ownership of that file descriptor from your code to the channel. A dispatch I/O channel takes control of its file descriptor so that it can reconfigure that file descriptor as needed. For example, the channel usually reconfigures the file descriptor with the O_NONBLOCK
flag so that subsequent read and write operations do not block the current thread. Creating a channel using a file path causes the channel to create the necessary file descriptor and take control of it.
- 要创建调度I / O通道,您必须提供文件描述符或要打开的文件的名称。 如果您已有一个打开的文件描述符,则将其传递给通道会将该文件描述符的所有权从代码更改为通道。 调度I / O通道控制其文件描述符,以便它可以根据需要重新配置该文件描述符。 例如,通道通常使用O_NONBLOCK标志重新配置文件描述符,以便后续的读写操作不会阻塞当前线程。 使用文件路径创建通道会导致通道创建必要的文件描述符并控制它。
Listing 7-1 shows a simple example of how to open a dispatch I/O channel using an NSURL
object. In this case, the channel is configured for random read-access and assigned to a custom property of the current class. The queue and block act to clean up the channel in the event that an error occurs during creation or at the end of the channel’s lifecycle. If an error occurs during creation, you can use the error code to determine what happened. An error code of 0 indicates that the channel relinquished control of its file descriptor normally, usually as a result of calling the dispatch_io_close function, and that you can now dispose of the channel safely.
- 清单7-1显示了如何使用NSURL对象打开调度I / O通道的简单示例。 在这种情况下,通道配置为随机读取访问并分配给当前类的自定义属性。 如果在创建期间或通道生命周期结束时发生错误,队列和块将用于清除通道。 如果在创建过程中发生错误,您可以使用错误代码来确定发生的情况。 错误代码为0表示通道正常放弃对其文件描述符的控制,通常是调用dispatch_io_close函数的结果,并且您现在可以安全地处理该通道。
Listing 7-1 Creating a dispatch I/O channel
-(void)openChannelWithURL:(NSURL*)anURL {
NSString* filePath = [anURL path];
self.channel = dispatch_io_create_with_path(DISPATCH_IO_RANDOM,
[filePath UTF8String], // Convert to C-string
O_RDONLY, // Open for reading
0, // No extra flags
dispatch_get_main_queue(),
^(int error){
// Cleanup code for normal channel operation.
// Assumes that dispatch_io_close was called elsewhere.
if (error == 0) {
dispatch_release(self.channel);
self.channel = NULL;
}
});
}
After creating a dispatch channel, you can store a reference to the resulting dispatch_io_t structure and use it to initiate read or write calls at your convenience. If you created a channel that supports random access, you can start reading or writing at any location. If you create a stream-based channel, any offset value you specify as a starting point is ignored and data is read or written at the current location. For example, to read the second 1024 bytes from a channel that supports random access, your read call might look similar to the following:
dispatch_io_read(self.channel,
1024, // 1024 bytes into the file
1024, // Read the next 1024 bytes
dispatch_get_main_queue(), // Process the bytes on the main thread
^(bool done, dispatch_data_t data, int error){
if (error == 0) {
// Process the bytes.
}
});
A write operation requires you to specify the bytes you want written to the file, the location at which to begin writing (for random access channels), and a handler block with which to receive progress reports. You initiate write operations using the dispatch_io_write function, which is described in Grand Central Dispatch (GCD) Reference.
- 写操作要求您指定要写入文件的字节,开始写入的位置(对于随机访问通道)以及用于接收进度报告的处理程序块。 您可以使用dispatch_io_write函数启动写入操作,该函数在Grand Central Dispatch(GCD)参考中有所描述。
Manipulating Dispatch Data for an I/O Channel
All channel-based operations use dispatch_data_t structures to manipulate the data read or written using a channel. A dispatch_data_t
structure is an opaque type that manages one or more contiguous memory buffers. The use of an opaque type allows GCD to use discontiguous buffers internally while still presenting the data to your app as if it were more or less contiguous. The actual implementation details of how dispatch data structures work is not important, but understanding how to create them or get data out of them is.
- 所有基于通道的操作都使用dispatch_data_t结构来操纵使用通道读取或写入的数据。 dispatch_data_t结构是一种opaque类型,用于管理一个或多个连续的内存缓冲区。 使用opaque类型允许GCD在内部使用不连续的缓冲区,同时仍然将数据呈现给您的应用程序,就像它或多或少是连续的一样。 调度数据结构如何工作的实际实现细节并不重要,但了解如何创建它们或从中获取数据是如此。
To write data to a dispatch I/O channel, your code must provide a dispatch_data_t
structure with the bytes to write. You do this using the dispatch_data_create function, which takes a pointer to a buffer and the size of the buffer and returns a dispatch_data_t
structure that encapsulates the data from that buffer. How the data object encapsulates the buffer depends on the destructor you provide when calling the dispatch_data_create
function. If you use the default destructor, the data object makes a copy of the buffer and takes care of releasing that buffer at the appropriate time. However, if you do not want the data object to copy the buffer you provide, you must provide a custom destructor block to handle any needed cleanup when the data object itself is released.
- 要将数据写入调度I / O通道,您的代码必须提供带有要写入的字节的dispatch_data_t结构。 您可以使用dispatch_data_create函数执行此操作,该函数获取指向缓冲区的指针和缓冲区的大小,并返回封装来自该缓冲区的数据的dispatch_data_t结构。 数据对象如何封装缓冲区取决于调用dispatch_data_create函数时提供的析构函数。 如果使用默认析构函数,则数据对象会生成缓冲区的副本,并负责在适当的时间释放该缓冲区。 但是,如果您不希望数据对象复制您提供的缓冲区,则必须提供自定义析构函数块,以便在释放数据对象本身时处理任何所需的清理。
Note: If you have multiple data buffers that you want to write to a file as a single contiguous block of data, you can create a single dispatch data object that represents all of those buffers. Using the dispatch_data_create_concat function, you can append additional data buffers to a dispatch_data_t
structure. The buffers themselves can all be independent and in different parts of memory but the dispatch data object collects them and represents them as a single entity. (You can even use the dispatch_data_create_map function to generate a contiguous version of your buffers.) Especially for disk-based operations, concatenating multiple buffers lets you write large amounts of data to a file using one call to the dispatch_io_write function, which is much more efficient than calling dispatch_io_write
separately for each independent buffer.
- 如果要将多个数据缓冲区作为单个连续数据块写入文件,则可以创建表示所有这些缓冲区的单个调度数据对象。 使用dispatch_data_create_concat函数,可以将附加数据缓冲区附加到dispatch_data_t结构。 缓冲区本身可以是独立的并且位于内存的不同部分,但是调度数据对象收集它们并将它们表示为单个实体。 (您甚至可以使用dispatch_data_create_map函数生成缓冲区的连续版本。)特别是对于基于磁盘的操作,连接多个缓冲区允许您使用一次调用dispatch_io_write函数将大量数据写入文件,这更多 比为每个独立缓冲区分别调用dispatch_io_write更有效。
To extract bytes from a dispatch data object, you use the dispatch_data_apply function. Because dispatch data objects are opaque, you use this function to iterate over the buffers in the object and process them using a block that you provide. For a dispatch data object with a single contiguous buffer, your block is called once. For a data object with multiple buffers, your block is called as many times as there are buffers. Each time your block is called, it is passed a data buffer and some information about that buffer.
- 要从分派数据对象中提取字节,请使用dispatch_data_apply函数。 由于调度数据对象是不透明的,因此使用此函数迭代对象中的缓冲区并使用您提供的块处理它们。 对于具有单个连续缓冲区的调度数据对象,将调用块一次。 对于具有多个缓冲区的数据对象,您的块被调用的次数与缓冲区一样多。 每次调用块时,都会传递一个数据缓冲区和一些有关该缓冲区的信息。
Listing 7-2 shows an example that opens a channel and reads a UTF8 formatted text file, creating NSString
objects for the contents of the file. This particular example reads 1024 bytes at a time, which is an arbitrary amount and may not yield the best performance. However, it does demonstrate the basic premise of how to use the dispatch_io_read function in combination with the dispatch_data_apply
function to read the bytes and then convert them into a form that your app might want. In this case, the block that processes the bytes uses the dispatch data object’s buffer to initialize a new string object. It then hands the string off to the custom addString:toFile:
method, which in this case would store it for later use.
- 清单7-2显示了一个示例,该示例打开一个通道并读取UTF8格式的文本文件,为该文件的内容创建NSString对象。 此特定示例一次读取1024个字节,这是一个任意数量,可能无法产生最佳性能。 但是,它确实演示了如何将dispatch_io_read函数与dispatch_data_apply函数结合使用来读取字节然后将它们转换为应用程序可能需要的表单的基本前提。 在这种情况下,处理字节的块使用调度数据对象的缓冲区来初始化新的字符串对象。 然后它将字符串移交给自定义的addString:toFile:方法,在这种情况下,它将存储它以供以后使用。
Listing 7-2 Reading the bytes from a text file using a dispatch I/O channel
- 使用调度I / O通道从文本文件中读取字节
- (void)readContentsOfFile:(NSURL*)anURL {
// Open the channel for reading.
NSString* filePath = [anURL path];
self.channel = dispatch_io_create_with_path(DISPATCH_IO_RANDOM,
[filePath UTF8String], // Convert to C-string
O_RDONLY, // Open for reading
0, // No extra flags
dispatch_get_main_queue(),
^(int error){
// Cleanup code
if (error == 0) {
dispatch_release(self.channel);
self.channel = nil;
}
});
// If the file channel could not be created, just abort.
if (!self.channel)
return;
// Get the file size.
NSNumber* theSize;
NSInteger fileSize = 0;
if ([anURL getResourceValue:&theSize forKey:NSURLFileSizeKey error:nil])
fileSize = [theSize integerValue];
// Break the file into 1024 size strings.
size_t chunkSize = 1024;
off_t currentOffset = 0;
for (currentOffset = 0; currentOffset < fileSize; currentOffset += chunkSize) {
dispatch_io_read(self.channel, currentOffset, chunkSize, dispatch_get_main_queue(),
^(bool done, dispatch_data_t data, int error){
if (error)
return;
// Build strings from the data.
dispatch_data_apply(data, (dispatch_data_applier_t)^(dispatch_data_t region,
size_t offset, const void *buffer, size_t size){
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSString* aString = [[[NSString alloc] initWithBytes:buffer
length:size encoding:NSUTF8StringEncoding] autorelease];
[self addString:aString toFile:anURL]; // Custom method.
[pool release];
return true; // Keep processing if there is more data.
});
});
}
}
For more information about the functions you use to manipulate dispatch data objects, see Grand Central Dispatch (GCD) Reference.
- 有关用于操作调度数据对象的函数的更多信息,请参阅Grand Central Dispatch(GCD)参考。
Reading and Writing Files Synchronously
The file-related interfaces that operate on data synchronously give you the flexibility to set your code’s execution context yourself. Just because they execute synchronously does not mean that they are less efficient than their asynchronous counterparts. On the contrary, it just means that the interface itself does not provide the asynchronous execution context automatically. If you want to read and write data asynchronously using these technologies, and get all the same benefits, you must provide the asynchronous execution context yourself. Of course, the best way to do that is to execute your code using GCD dispatch queues or operation objects.
Building Your Content in Memory and Writing It to Disk All at Once
The simplest way to manage the reading and writing of file data is all at once. This works well for custom document types where you expect the size of the document’s on-disk representation to remain reasonably small. You would not want to do this for multimedia files or for files whose size can grow to many megabytes in size.
- 管理文件数据读写的最简单方法是同时进行。 这适用于自定义文档类型,您希望文档的磁盘表示大小保持相当小。 您不希望对多媒体文件或大小可能增长到几兆字节的文件执行此操作。
For custom document types that use a binary or private file format, you can use the NSData or NSMutableData class to transfer your custom data to and from disk. You can create new data objects in many different ways. For example, you can use a keyed archiver object to convert a graph of objects into a linear stream of bytes enclosed in a data object. If you have a binary file format that is very structured, you can append bytes to an NSMutableData
object and build your data object piece by piece. When you are ready to write the data object to disk, use the writeToURL:atomically: or writeToURL:options:error: method. These methods allow you to create the corresponding on-disk file in one step.
- 对于使用二进制或专用文件格式的自定义文档类型,可以使用NSData或NSMutableData类将自定义数据传入和传出磁盘。 您可以通过多种不同方式创建新数据对象。 例如,您可以使用键控归档器对象将对象图转换为数据对象中包含的线性字节流。 如果您的二进制文件格式非常结构化,则可以将字节附加到NSMutableData对象并逐个构建数据对象。 准备好将数据对象写入磁盘时,请使用writeToURL:atomically:或writeToURL:options:error:方法。 这些方法允许您一步创建相应的磁盘文件。
Note: In iOS, one of the options you can pass to the writeToURL:options:error: method allows you to specify whether you want the contents of the file encrypted. If you specify one of these options, the contents are encrypted immediately to ensure the security of the file.
- 在iOS中,您可以传递给writeToURL的选项之一:options:error:方法允许您指定是否要加密文件的内容。 如果指定其中一个选项,则会立即加密内容以确保文件的安全性。
To read data back from disk, use the initWithContentsOfURL:options:error: method to obtain a data object based on the contents of your file. You can use this data object to reverse the process you used when creating it. Thus, if you used a keyed archiver to create the data object, you can use a keyed unarchiver to re-create your object graph. If you wrote the data out piece by piece, you can parse the byte stream in the data object and use it to reconstruct your document’s data structures.
- 要从磁盘读取数据,请使用initWithContentsOfURL:options:error:方法根据文件内容获取数据对象。 您可以使用此数据对象来反转创建它时使用的过程。 因此,如果您使用键控归档器来创建数据对象,则可以使用键控的归档器来重新创建对象图。 如果您逐个编写数据,则可以解析数据对象中的字节流,并使用它来重建文档的数据结构。
Apps that use the NSDocument infrastructure typically interact with the file system indirectly using NSData
objects. When the user saves a document, the infrastructure prompts the corresponding NSDocument
object for a data object to write to disk. Similarly, when the user opens an existing document, it creates the document object and passes it a data object with which to initialize itself.
- 使用NSDocument基础结构的应用程序通常使用NSData对象间接与文件系统交互。 当用户保存文档时,基础结构会提示相应的NSDocument对象以使数据对象写入磁盘。 类似地,当用户打开现有文档时,它会创建文档对象并向其传递一个数据对象,用于初始化自身。
For more information about the NSData
and NSMutableData
classes, see Foundation Framework Reference. For more information about using the document infrastructure in a macOS app, see Mac App Programming Guide.
- 有关NSData和NSMutableData类的更多信息,请参阅Foundation Framework Reference。 有关在macOS应用程序中使用文档基础结构的更多信息,请参阅“Mac应用程序编程指南”。
Reading and Writing Files Using NSFileHandle
- 使用NSFileHandle读取和写入文件
The use of the NSFileHandle class closely parallels the process for reading and writing files at the POSIX level. The basic process is that you open the file, issue read or write calls, and close the file when you are done. In the case of NSFileHandle
, you open the file automatically when you create an instance of the class. The file handle object acts as a wrapper for the file, managing the underlying file descriptor for you. Depending on whether you requested read access, write access, or both, you then call the methods of NSFileHandle
to read or write actual bytes. When you are done, you release your file handle object to close the file.
- NSFileHandle类的使用与在POSIX级别读取和写入文件的过程非常相似。 基本过程是您打开文件,发出读取或写入调用,并在完成后关闭文件。 对于NSFileHandle,您在创建类的实例时自动打开文件。 文件句柄对象充当文件的包装器,为您管理基础文件描述符。 根据您是否请求读访问,写访问或两者,您可以调用NSFileHandle的方法来读取或写入实际字节。 完成后,释放文件句柄对象以关闭文件。
Listing 7-3 shows a very simple method that reads the entire contents of a file using a file handle object. The fileHandleForReadingFromURL:error: method creates the file handle object as an autoreleased object, which causes it to be released automatically at some point after this method returns.
- 清单7-3显示了一个非常简单的方法,它使用文件句柄对象读取文件的全部内容。 fileHandleForReadingFromURL:error:方法将文件句柄对象创建为自动释放的对象,这会导致在此方法返回后的某个时刻自动释放它。
Listing 7-3 Reading the contents of a file using NSFileHandle
- 使用NSFileHandle读取文件的内容
- (NSData*)readDataFromFileAtURL:(NSURL*)anURL {
NSFileHandle* aHandle = [NSFileHandle fileHandleForReadingFromURL:anURL error:nil];
NSData* fileContents = nil;
if (aHandle)
fileContents = [aHandle readDataToEndOfFile];
return fileContents;
}
For more information about the methods of the NSFileHandle
class, see NSFileHandle Class Reference.
Managing Disk Reads and Writes at the POSIX Level
If you prefer to use C-based functions for your file management code, the POSIX layer offers standard functions for working with files. At the POSIX level, you identify a file using a file descriptor, which is an integer value that identifies an open file uniquely within your app. You pass this file descriptor to any subsequent functions that require it. The following list contains the main POSIX functions you use to manipulate files:
如果您更喜欢将基于C的函数用于文件管理代码,则POSIX层提供用于处理文件的标准函数。 在POSIX级别,您使用文件描述符标识文件,该文件描述符是一个整数值,用于标识应用程序中唯一的打开文件。 您将此文件描述符传递给任何需要它的后续函数。 以下列表包含用于处理文件的主要POSIX函数:
-
Use the
open
function to obtain a file descriptor for your file.- 使用open函数获取文件的文件描述符。
-
Use the
pread
,read
, orreadv
function to read data from an open file descriptor. For information about these functions, see pread.- 使用pread,read或readv函数从打开的文件描述符中读取数据。 有关这些功能的信息,请参阅pread。
-
Use the
pwrite
,write
, orwritev
function to write data to an open file descriptor. For information about these functions, see pwrite.- 使用pwrite,write或writev函数将数据写入打开的文件描述符。 有关这些函数的信息,请参阅pwrite。
-
Use the lseek function to reposition the current file pointer and change the location at which you read or write data.
- 使用lseek函数重新定位当前文件指针并更改读取或写入数据的位置。
Use the pclose function to close the file descriptor when you are done with it.
- 完成后,使用pclose函数关闭文件描述符。
Important: On some file systems, there is no guarantee that a successful write call results in the actual writing of bytes to the file system. For some network file systems, written data might be sent to the server at some point after your write call. To verify that the data actually made it to the file, use the fsync function to force the data to the server or close the file and make sure it closed successfully—that is, the close
function did not return -1
.
- 在某些文件系统上,无法保证成功的写入调用会导致实际将字节写入文件系统。 对于某些网络文件系统,写入数据可能会在您的写入调用后的某个时刻发送到服务器。 要验证数据是否实际存入文件,请使用fsync函数强制数据到服务器或关闭文件并确保它成功关闭 - 也就是说,close函数没有返回-1。
Listing 7-4 shows a simple function that uses POSIX calls to read the first 1024 bytes of a file and return them in an NSData object. If the file has fewer than 1024 bytes, the method reads as many bytes as possible and truncates the data object to the actual number of bytes.
- 清单7-4显示了一个简单的函数,它使用POSIX调用来读取文件的前1024个字节并将它们返回到NSData对象中。 如果文件少于1024个字节,则该方法将读取尽可能多的字节,并将数据对象截断为实际的字节数。
Listing 7-4 Reading the contents of a file using POSIX functions
使用POSIX函数读取文件的内容
- (NSData*)readDataFromFileAtURL:(NSURL*)anURL {
NSString* filePath = [anURL path];
fd = open([filePath UTF8String], O_RDONLY);
if (fd == -1)
return nil;
NSMutableData* theData = [[[NSMutableData alloc] initWithLength:1024] autorelease];
if (theData) {
void* buffer = [theData mutableBytes];
NSUInteger bufferSize = [theData length];
NSUInteger actualBytes = read(fd, buffer, bufferSize);
if (actualBytes < 1024)
[theData setLength:actualBytes];
}
close(fd);
return theData;
Because there is a limit to the number of open file descriptors an app may have at any given time, you should always close file descriptors as soon as you are done using them. File descriptors are used not only for open files but for communications channels such as sockets and pipes. And your code is not the only entity creating file descriptors for your app. Every time you load a resource file or use a framework that communicates over the network, the system creates a file descriptor on behalf of your code. If your code opens large numbers of sockets or files and never closes them, system frameworks may not be able to create file descriptors at critical times.
- 由于应用程序在任何给定时间可能具有的打开文件描述符的数量有限,因此您应该在使用它们后立即关闭文件描述符。 文件描述符不仅用于打开文件,还用于通信通道,如套接字和管道。 而且您的代码不是为您的应用创建文件描述符的唯一实体。 每次加载资源文件或使用通过网络进行通信的框架时,系统都会代表您的代码创建文件描述符。 如果您的代码打开大量套接字或文件而从不关闭它们,系统框架可能无法在关键时刻创建文件描述符。
Getting and Setting File Metadata Information
Files contain a lot of useful information but so does the file system. For each file and directory, the file system stores meta information about things like the item’s size, creation date, owner, permissions, whether a file is locked, or whether a file’s extension is hidden. There are several ways to get and set this information but the most prominent ways are:
文件包含许多有用的信息,但文件系统也是如此。 对于每个文件和目录,文件系统存储有关项目大小,创建日期,所有者,权限,文件是否被锁定或文件扩展名是否隐藏等内容的元信息。 有几种方法可以获取和设置此信息,但最突出的方法是:
-
Get and set most content metadata information (including Apple-specific attributes) using the NSURL class.
- 使用NSURL类获取和设置大多数内容元数据信息(包括Apple特定的属性)。
-
Get and set basic file-related information using the NSFileManager class.
- 使用NSFileManager类获取并设置与文件相关的基本信息。
The NSURL
class offers a wide range of file-related information, including information that is standard for the file system (such as file size, type, owner, and permissions) but also a lot of Apple-specific information (such as the assigned label, localized name, the icon associated with the file, whether the file is a package, and so on). In addition, some methods that take URLs as arguments allow you to cache attributes while you are performing other operations on the file or directory. Especially when accessing large numbers of files, this type of caching behavior can improve performance by minimizing the number of disk-related operations. Regardless of whether attributes are cached, you retrieve them from the NSURL object using its getResourceValue:forKey:error: method and set new values for some attributes using the setResourceValue:forKey:error:` or setResourceValues:error: method.
- NSURL类提供各种与文件相关的信息,包括文件系统的标准信息(例如文件大小,类型,所有者和权限),以及许多Apple特定的信息(例如指定的标签) ,本地化名称,与文件关联的图标,文件是否为包,等等。 此外,某些将URL作为参数的方法允许您在对文件或目录执行其他操作时缓存属性。 特别是在访问大量文件时,这种类型的缓存行为可以通过最小化与磁盘相关的操作数来提高性能。 无论属性是否被缓存,您都可以使用其getResourceValue:forKey:error:方法从NSURL对象中检索它们,并使用setResourceValue:forKey:error:`或setResourceValues:error:方法为某些属性设置新值。
Even if you are not using the NSURL
class, you can still get and set some file-related information using the attributesOfItemAtPath:error: and setAttributes:ofItemAtPath:error: methods of the NSFileManager
class. These methods let you retrieve information about file system items like their type, size, and the level of access currently supported. You can also use the NSFileManager
class to retrieve more general information about the file system itself, such as its size, the amount of free space, the number of nodes in the file system, and so on. Note that you are only able to get a subset of resources for iCloud files.
- 即使您没有使用NSURL类,仍然可以使用attributesOfItemAtPath:error:和setAttributes:ofItemAtPath:error:NSFileManager类的方法来获取和设置一些与文件相关的信息。 通过这些方法,您可以检索有关文件系统项的信息,例如其类型,大小和当前支持的访问级别。 您还可以使用NSFileManager类来检索有关文件系统本身的更多常规信息,例如其大小,可用空间量,文件系统中的节点数等。 请注意,您只能获取iCloud文件的一部分资源。
For more information about the NSURL
and NSFileManager
classes, and the attributes you can obtain for files and directories, see NSURL Class Reference and NSFileManager Class Reference.
- 有关NSURL和NSFileManager类的更多信息,以及可以为文件和目录获取的属性,请参阅NSURL类参考和NSFileManager类参考。