Методы для чтения и записи файлов без координаторов файла

Чтение и запись файлов включают передачу последовательности байтов между Вашим кодом и базовым диском. Это - форма самого низкого уровня управления файлами, но является также основой для более сложных методов также. В некоторый момент даже самые сложные структуры данных должны быть превращены в последовательность байтов, прежде чем они смогут быть сохранены на диске. Точно так же те же самые данные должны быть считаны из диска как последовательность байтов, прежде чем это сможет использоваться для восстановления более сложных структур данных, которые это представляет.

Существует несколько различных технологий для чтения и записи содержания файлов, почти все из которых поддерживаются и iOS и OS X. Все они делают по существу ту же вещь, но немного отличающимися способами. Некоторые технологии требуют, чтобы Вы считали и записали данные файла последовательно, в то время как другие могут позволить Вам переходить вокруг и воздействовать на только часть файла. Некоторые технологии предоставляют автоматическую поддержку для чтения и записи асинхронно, в то время как другие выполняются синхронно так, чтобы Вы имели больше контроля над их выполнением.

Выбор из доступных технологий является вопросом решения, сколько управления Вы хотите по чтению и записи и сколько усилия Вы хотите потратить запись Вашего кода управления файлами. Высокоуровневые технологии как потоки Какао ограничивают Вашу гибкость, но обеспечивают простой в использовании интерфейс. Технологии низшего уровня как POSIX и Grand Central Dispatch (GCD) дают Вам максимальную гибкость и питание, но требуют, чтобы Вы записали немного больше кода.

Чтение и запись файлов асинхронно

Поскольку операции файла включают доступ к диску (возможно один на сетевом сервере), выполнение тех операций асинхронно почти всегда предпочитается. Технологии, такие как потоки Какао и Grand Central Dispatch (GCD) разработаны для выполнения асинхронно в любом случае, который позволяет Вам фокусироваться на чтении и записи данных файла вместо того, чтобы волноваться о том, где выполняется Ваш код.

Обработка всего файла линейно Используя потоки

Если Вы всегда читаете или пишете содержание файла от начала до конца, потоки обеспечивают простой интерфейс для того, чтобы сделать так асинхронно. Потоки обычно используются для управления сокетами и другими типами источников, где данные могут стать доступными в течение долгого времени. Однако можно также использовать потоки, чтобы считать или записать весь файл в одном или более пакетах. Существует два типа доступных потоков:

  • Используйте NSOutputStream записать данные последовательно в диск.

  • Используйте NSInputStream возразите для чтения данных последовательно из диска.

Потоковые объекты используют цикл выполнения текущего потока для планирования операций чтения и операций записи на потоке. Когда существуют данные, ожидающие, чтобы быть считанными, входной поток будит цикл выполнения и уведомляет его делегата. Когда существует пространство, доступное для записи данных, поток вывода будит цикл выполнения и уведомляет его делегата. При работе на файлы это поведение обычно означает, что цикл выполнения несколько раз будится в быстрой преемственности так, чтобы код делегата мог считать или записать данные файла. Это также означает, что Ваш код делегата неоднократно вызывают, пока Вы не закрываете потоковый объект, или в случае входных потоков, пока поток не достигает конца файла.

Для получения информации и примеры о том, как установить и использовать потоковые объекты, чтобы считать и записать данные, см. Потоковое Руководство по программированию.

Обработка файла Используя GCD

Центральная Отгрузка обеспечивает несколько различных способов считать или записать содержание файлов асинхронно:

  • Создайте отгрузку канал I/O и используйте его, чтобы считать или записать данные.

  • Используйте dispatch_read или dispatch_write удобство функционирует для выполнения единственной асинхронной работы.

  • Создайте источник отгрузки для планирования выполнения пользовательского блока обработчика событий, в котором Вы используете стандартные вызовы POSIX, чтобы считать или записать данные из файла.

Каналы I/O отгрузки являются предпочтительным способом считать и записать файлы, потому что они дают Вам прямой контроль, когда операции файла происходят, но все еще позволяют Вам обрабатывать данные асинхронно на очереди отгрузки. Отгрузка канал I/O dispatch_io_t структура, идентифицирующая файл, содержание которого Вы хотите считать или записать. Каналы могут быть сконфигурированы для доступа на основе потоков или произвольного доступа файлов. Канал на основе потоков вынуждает Вас считать или записать данные файла последовательно, тогда как канал случайного доступа позволяет Вам читать или записать при любом смещении с начала файла.

Если Вы не хотите проблемы создать и управлять отгрузкой канал I/O, можно использовать dispatch_read или dispatch_write функции для выполнения единственной операции чтения или операции записи на дескрипторе файла. Эти методы удобны для ситуаций, где Вы не хотите или нуждаетесь в издержках создания и управления отгрузкой канал I/O. Однако необходимо использовать их только при выполнении единственной операции чтения или операции записи на файле. Если необходимо выполнить многократные операции на том же файле, создав отгрузку, канал I/O намного более эффективен.

Источники отгрузки позволяют Вам обрабатывать файлы в пути, который подобен потоковым объектам Какао. Как потоковые объекты, они используются чаще с сокетами или источниками данных, отправляющими и получающими данные спорадически, но они могут все еще использоваться с файлами. Источник отгрузки планирует свой связанный блок обработчика событий каждый раз, когда существуют данные, ожидающие, чтобы быть считанными или пространство, доступное для записи. Для файлов это обычно приводит к блоку, запланированному неоднократно и к быстрой преемственности, пока Вы явно не отменяете источник отгрузки, или это достигает конца файла, который это читает.

Для получения дополнительной информации о создании и использовании источников отгрузки, см. Руководство по программированию Параллелизма. Для получения информации о dispatch_read или dispatch_write функции или любые другие функции GCD, видят Ссылку Grand Central Dispatch (GCD).

Создавая и Используя отгрузку канал I/O

Для создания отгрузки канал I/O необходимо обеспечить или дескриптор файла или имя файла, который Вы хотите открыть. Если у Вас уже есть открытый дескриптор файла, передавание его к каналу изменяет владение того дескриптора файла от Вашего кода до канала. Канал I/O отгрузки берет под свой контроль свой дескриптор файла так, чтобы он мог реконфигурировать тот дескриптор файла по мере необходимости. Например, канал обычно реконфигурировал дескриптор файла с O_NONBLOCK отметьте так, чтобы последующие операции чтения и операции записи не блокировали текущий поток. Создание канала с помощью пути к файлу заставляет канал создавать необходимый дескриптор файла и брать под свой контроль его.

Перечисление 7-1 показывает простой пример того, как открыть отгрузку канал I/O с помощью NSURL объект. В этом случае канал сконфигурирован для случайного доступа для чтения и присвоен пользовательскому свойству текущего класса. Очередь и блок действуют для очистки канала, если ошибка происходит во время создания или в конце жизненного цикла канала. Если ошибка происходит во время создания, можно использовать код ошибки для определения то, что произошло. Код ошибки 0 указывает, что канал оставил управление своего дескриптора файла обычно, обычно в результате вызова dispatch_io_close функция, и что можно теперь избавиться от канала безопасно.

Перечисление 7-1  , Создающее отгрузку канал I/O

-(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;
                         }
                      });
}

После создания канала отгрузки можно сохранить ссылку на получающееся dispatch_io_t структура и использование это для инициирования чтения или записи вызывает когда вам будет удобно. Если Вы создали канал, поддерживающий произвольный доступ, можно начать читать или писать в любом расположении. При создании канала на основе потоков какое-либо значение смещения, которое Вы указываете, поскольку начальная точка проигнорирована и данные считаны или записаны в текущем расположении. Например, для чтения вторых 1 024 байтов из канала, поддерживающего произвольный доступ вызов чтения мог бы выглядеть подобным следующему:

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.
                     }
                 });

Операция записи требует, чтобы Вы указали байты, которые Вы хотите записанный в файл, расположение, в котором можно начать писать (для каналов случайного доступа), и блок обработчика, с которым можно получить отчеты о ходе работ. Вы инициируете операции записи с помощью dispatch_io_write функция, описанная в Ссылке Grand Central Dispatch (GCD).

Управление данными отгрузки для канала I/O

Все основанное на канале использование операций dispatch_data_t структуры для управления чтением данных или записанным использованием канала. A dispatch_data_t структура является непрозрачным типом, управляющим одним или более непрерывными буферами памяти. Использование непрозрачного типа позволяет GCD использовать несмежные буферы внутренне, все еще представляя данные Вашему приложению, как будто это было более или менее непрерывно. Подробные данные фактической реализации того, как работа структур данных отгрузки не важна, но понимающий, как создать их или вытащить данные из них.

Для записи данных в отгрузку канал I/O код должен обеспечить a dispatch_data_t структура с байтами для записи. Вы делаете это использование dispatch_data_create функция, берущая указатель на буфер и размер буфера и возвращающая a dispatch_data_t структура, инкапсулирующая данные от того буфера. То, как объект данных инкапсулирует буфер, зависит от деструктора, который Вы обеспечиваете при вызове dispatch_data_create функция. При использовании деструктора по умолчанию объект данных делает копию буфера и заботится о выпуске того буфера в подходящее время. Однако, если Вы не хотите объект данных скопировать буфер, Вы обеспечиваете, необходимо обеспечить пользовательский блок деструктора для обработки любой необходимой очистки, когда выпущен сам объект данных.

Для извлечения байтов из объекта данных отгрузки Вы используете dispatch_data_apply функция. Поскольку объекты данных отгрузки непрозрачны, Вы используете эту функцию, чтобы выполнить итерации по буферам в объекте и обработать их использующий блок, который Вы обеспечиваете. Для объекта данных отгрузки с единственным непрерывным буфером Ваш блок вызывают один раз. Для объекта данных с многократными буферами Ваш блок вызывают так же много раз, как существуют буферы. Каждый раз, когда Ваш блок вызывают, он передается буфер данных и некоторая информация о том буфере.

Перечисление 7-2 показывает пример, открывающий канал и читающий файл форматированного текста UTF8, создавая NSString объекты для содержания файла. Этот определенный пример читает 1 024 байта за один раз, который является произвольной суммой и может не привести к лучшей производительности. Однако это действительно демонстрирует основную предпосылку того, как использовать dispatch_io_read функция в сочетании с dispatch_data_apply функционируйте, чтобы считать байты и затем преобразовать их в форму, которую могло бы хотеть Ваше приложение. В этом случае блок, обрабатывающий байты, использует буфер объекта данных отгрузки для инициализации нового строкового объекта. Это тогда передает строку к пользовательскому addString:toFile: метод, который в этом случае сохранил бы его для более позднего использования.

Перечисление 7-2  Читая байты из текстового файла с помощью отгрузки канал 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.
                        });
 
                     });
    }
}

Для получения дополнительной информации о функциях Вы используете, чтобы управлять объектами данных отгрузки, видеть Ссылку Grand Central Dispatch (GCD).

Чтение и запись файлов синхронно

Связанные с файлом интерфейсы, воздействующие на данные синхронно, дают Вам гибкость для установки контекста выполнения кода самим. Просто, потому что они выполняются, синхронно не означает, что они менее эффективны, чем их асинхронные дубликаты. Наоборот, это просто означает, что сам интерфейс не обеспечивает асинхронный контекст выполнения автоматически. Если Вы хотите считать и записать данные асинхронно с помощью этих технологий и стать всеми одинаковыми преимущества, необходимо обеспечить асинхронный контекст выполнения сами. Конечно, лучший способ сделать, который должен выполнить Ваш код с помощью очередей отгрузки GCD или объектов операции.

Создание содержания в памяти и запись его к диску одновременно

Самый простой способ управлять чтением и записью данных файла одновременно. Это работает хорошо на пользовательские типы документов, где Вы ожидаете, что размер дискового представления документа останется довольно маленьким. Вы не хотели бы делать это для мультимедийных файлов или для файлов, размер которых может расти до многих мегабайтов в размере.

Для пользовательских типов документов, использующих двоичный или частный формат файла, можно использовать NSData или NSMutableData класс для передачи пользовательских данных и от диска. Можно создать новые объекты данных многими различными способами. Например, можно использовать включенный объект archiver преобразовать график объектов в линейный поток байтов, включенных в объект данных. Если у Вас есть формат двоичного файла, очень структурированный, можно добавить байты к NSMutableData возразите и создайте свою часть объекта данных частью. Когда Вы будете готовы записать объект данных в диск, используйте writeToURL:atomically: или writeToURL:options:error: метод. Эти методы позволяют Вам создавать соответствующий дисковый файл за один шаг.

Для чтения данных назад из диска используйте initWithContentsOfURL:options:error: метод для получения объекта данных на основе содержания файла. Можно использовать этот объект данных инвертировать процесс, который Вы использовали при создании его. Таким образом при использовании включенного archiver для создания объекта данных, можно использовать включенный unarchiver для воссоздавания графа объектов. Если Вы записали данным часть частью, можно проанализировать поток байтов в объекте данных и использовать его для восстановления структур данных документа.

Приложения, использующие NSDocument инфраструктура обычно взаимодействует с файловой системой косвенно с помощью NSData объекты. Когда пользователь сохраняет документ, инфраструктура запрашивает соответствие NSDocument объект для объекта данных записать в диск. Точно так же, когда пользователь открывает существующий документ, он создает объект документа и передает его объект данных, с которым можно инициализировать себя.

Для получения дополнительной информации о NSData и NSMutableData классы, посмотрите Ссылку Платформы Основы. Для получения дополнительной информации об использовании инфраструктуры документа в приложении OS X см. Руководство по программированию Приложения Mac.

Чтение и запись файлов Используя NSFileHandle

Использование NSFileHandle класс близко параллелен процессу для чтения и записи файлов на уровне POSIX. Базовый процесс состоит в том, что Вы открываете файл, чтение проблемы или вызовы записи, и закрываете файл, когда Вы сделаны. В случае NSFileHandle, Вы открываете файл автоматически при создании экземпляра класса. Объект дескриптора файла действует как обертка для файла, управляя базовым дескриптором файла для Вас. В зависимости от того, запросили ли Вы доступ для чтения, доступ для записи или обоих, Вы тогда вызываете методы NSFileHandle считать или записать фактические байты. Когда Вы сделаны, Вы выпускаете свой объект дескриптора файла закрыть файл.

Перечисление 7-3 показывает очень простой метод, читающий все содержание файла с помощью объекта дескриптора файла. fileHandleForReadingFromURL:error: метод создает объект дескриптора файла как автовыпущенный объект, заставляющий его быть выпущенным автоматически в некоторый момент после этого метода возвраты.

Перечисление 7-3  Читая содержание использования файла NSFileHandle

- (NSData*)readDataFromFileAtURL:(NSURL*)anURL {
    NSFileHandle* aHandle = [NSFileHandle fileHandleForReadingFromURL:anURL error:nil];
    NSData* fileContents = nil;
 
    if (aHandle)
        fileContents = [aHandle readDataToEndOfFile];
 
    return fileContents;
}

Для получения дополнительной информации о методах NSFileHandle класс, посмотрите Ссылку класса NSFileHandle.

Управление чтением с диска и записями на уровне POSIX

Если Вы предпочитаете использовать функции на базе С для своего кода управления файлами, уровень POSIX предлагает стандартные функции для работы с файлами. На уровне POSIX Вы идентифицируете файл с помощью дескриптора файла, который является целочисленным значением, идентифицирующим открытый файл уникально в приложении. Вы передаете этот дескриптор файла любым последующим функциям, требующим его. Следующий список содержит основные функции POSIX, которые Вы используете для управления файлами:

  • Используйте open функция для получения дескриптора файла для файла.

  • Используйте pread, read, или readv функционируйте для чтения данных из открытого дескриптора файла. Для получения информации об этих функциях посмотрите pread.

  • Используйте pwrite, write, или writev функционируйте для записи данных в открытый дескриптор файла. Для получения информации об этих функциях посмотрите pwrite.

  • Используйте lseek функционируйте, чтобы изменить местоположение текущего указателя файла и изменить местоположение, в котором Вы читаете или пишете данные.

  • Используйте pclose функционируйте для закрытия дескриптора файла, когда Вы будете сделаны с ним.

Перечисление 7-4 показывает простую функцию, использующую вызовы POSIX, чтобы считать первые 1 024 байта файла и возвратить их в NSData объект. Если файл имеет меньше чем 1 024 байта, чтения метода как можно больше байтов и усекает объект данных к фактическому числу байтов.

Перечисление 7-4  Читая содержание файла с помощью функций 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;
}

Поскольку существует предел числу открытых дескрипторов файлов, которые приложение может иметь в любой момент времени, необходимо всегда закрывать дескрипторы файлов, как только Вы сделаны с помощью них. Дескрипторы файлов используются не только для открытых файлов, но и для каналов передачи, таких как сокеты и каналы. И Ваш код не является единственным объектом, создающим дескрипторы файлов для Вашего приложения. Каждый раз Вы загружаете файл ресурсов или используете платформу, связывающуюся по сети, система создает дескриптор файла от имени Вашего кода. Если Ваш код открывает большие количества сокетов или файлов и никогда не закрывает их, системные платформы могут не быть в состоянии создать дескрипторы файлов в критические времена.

Получение и установка информации о метаданных файла

Файлы содержат много полезной информации, но файловая система - также. Для каждого файла и каталога, файловая система хранит метаинформацию о вещах как размер элемента, дата создания, владелец, полномочия, заблокирован ли файл, или скрыто ли расширение файла. Существует несколько способов получить и установить эту информацию, но самые видные пути:

NSURL класс предлагает широкий диапазон связанной с файлом информации, включая информацию, которая является стандартной для файловой системы (такой как размер файла, тип, владелец и полномочия), но также и большая специфичная для Apple информация (такие как присвоенная метка, локализованное имя, значок, связанный с файлом, является ли файл пакетом, и т.д.). Кроме того, некоторые методы, берущие URLs в качестве параметров, позволяют, Вы к кэшу приписываете при выполнении других операций на файле или каталоге. Особенно при доступе к большим количествам файлов, этот тип кэширующегося поведения может улучшить производительность путем минимизации числа связанных с диском операций. Независимо от того, кэшируются ли атрибуты, Вы получаете их от объекта NSURL использование getResourceValue:forKey:error: метод и набор новые значения для некоторых атрибутов с помощью setResourceValue:forKey:error: или setResourceValues:error: метод.

Даже если Вы не используете NSURL класс, можно все еще получить и установить некоторую связанную с файлом информацию с помощью attributesOfItemAtPath:error: и setAttributes:ofItemAtPath:error: методы NSFileManager класс. Эти методы позволяют Вам получить информацию об элементах файловой системы как их тип, размер и уровень доступа, в настоящее время поддерживаемого. Можно также использовать NSFileManager класс для получения более общей информации о самой файловой системе, такой как ее размер, сумма свободного пространства, число узлов в файловой системе, и т.д. Обратите внимание на то, что Вы только в состоянии получить подмножество ресурсов для файлов iCloud.

Для получения дополнительной информации о NSURL и NSFileManager классы и атрибуты, которые можно получить для файлов и каталогов, видят Ссылку класса Ссылки класса и NSFileManager NSURL.