Текст книги "Программирование на Objective-C 2.0"
Автор книги: Стивен Кочан
Жанр:
Программирование
сообщить о нарушении
Текущая страница: 25 (всего у книги 32 страниц)
Глава 19. Архивация
В терминологии Objective-C архивация – это процесс сохранения одного или нескольких объектов в формате, позволяющем восстановить их в дальнейшем. Часто при этом объекты записываются в файл, чтобы их можно было прочитать. Мы рассмотрим в этой главе два метода архивации данных: списки свойств (property list) и кодирование с ключами (key-valued coding). 19.1. Архивация со списками свойств XML
В приложениях Mac OS X используются списки свойств XML (propertylist или plists) для сохранения такой информации, как настройки по умолчанию, настройки приложений и данные конфигурации, поэтому вам будет полезно узнать, как их создавать и считывать. Однако их использование в целях архивации ограничено, поскольку при создании списка свойств для структуры данных конкретные классы объектов не удерживаются, информация о нескольких ссылках на один объект и мутабельность объекта не сохраняются.
Примечание. Формат в списках свойств в так называемом «старом стиле» отли-чается от формата списков свойств XML. По возможности старайтесь придер-живаться списков свойств XML.
При записи данных в файл для объектов типа NSString, NSDictionary, NSArray, NSDate, NSData или NSNumber можно использовать реализованный в этих классах метод writeToFile:atomically:. При записи словаря или массива этот метод записывает данные в файл в формате списка свойств XML. В программе 19.1 показано, как записать в файл в виде списка свойств словарь, созданный в главе 15. #import
Сообщение writeToFile:atomically:encoding:enror: передается объекту-словарю glossary, что вызывает запись этого словаря в файл glossary в виде списка свойств. Параметру atomically присваивается значение YES, указывая, что операцию записи нужно выполнять сначала во временный резервный файл; если эта запись выпол-нена успешно, данные окончательно перемещаются в указанный файл с именем glossary. Эта мера защищает файл от повреждения, например, при сбое системы во время записи. В этом случае прежний файл glossary (если он уже существовал) не будет поврежден.
При просмотре содержимого файла glossary, созданного программой 19.1, мы увидим следующее.
XML-файл, созданный для этого словаря, содержит набор из пар ключей (
При создании списка свойств из словаря все ключи в этом словаре должны быть объектами NSString. Элементами массива или значениями в словаре могут быть объекты типа NSString, NSArray, NSDictionary, NSData, NSDate или NSNumber.
Для считывания данных используйте метод dataWithContentsOfFile:; для считы-вания строковых объектов используйте метод stringWithContentsOfFile:. В программе 19.2 выполняется считывание словаря, записанного в программе 19.1, и последующий вывод его содержимого. #import
Вывод программы 19.2 archiving: Storing an object for later use (архивация: сохранение объекта для дальнейшего использования) abstract class: A class defined so other classes can inherit from it (абстрактный класс: Класс, определенный таким образом, чтобы другие классы могли наследовать из него) adopt: То implement all the methods defined in a protocol (принять: для реализации всех методов, определенных в протоколе)
Ваши списки свойств не обязательно должны создаваться из программы на Objective-C; список свойств может поступать из любою источника. Вы можете создавать свои собственные списки свойств с помощью простою текстового ре-дактора или программы Property List Editor (Редактор списков свойств), которая находится в /Developer/Applications/Utilities на компьютерах с системой Mac OS X. 19.2. Архивация с помощью NSKeyedArchiver
В файле можно сохранять объекты любого типа, а не только строки, массивы и словари. Для этого необходимо создать архив с ключами (keyed archive) с помо-щью класса NSKeyedArchiver.
Mac OX X поддерживает архивы с ключами, начиная с версии I0.2. В ранних версиях с помощью класса NSArchiver создавались последовательные архивы (sequential archives). Данные последовательных архивов должны считываться точно в том же порядке, в каком они записывались.
В архиве с ключами каждое поле архива имеет имя. При архивации объекта мы присваиваем ему имя, или ключ. При считывании объекта из архива мы ис-пользуем тот же ключ. Это позволяет записывать объекты в архив и считывать их в любом порядке. Кроме того, есл и в классе добавляются или удаляются новые переменные экземпляра, это можно предусмотреть в программе.
Отметим, что NSArchiver недоступен в iPhone SDK. Если нужна архивация в iPhone, вы должны использовать NSKeyedArchiver.
Чтобы использовать архивы с ключами, нужно импортировать
В программе 19.3 показано, как сохранять файл на диске с помощью метода archiveRootObjectrtoRle: из класса NSKeyedArchiver. Чтобы использовать этот класс, нужно включить в программу соответствующий файл с помощью оператора #import
Программа 19.3 не выводит никаких данных на терминал. Однако оператор [NSKeyedArchiver archiveRootObject: glossary toFile: @"glossary.archive"];
записывает словарь glossary в файл glossary.archive. Для этого файла можно задать любой путь. В данном случае файл записывается в текущую папку.
Этот архивный файл можно в дальнейшем читать в программу с помощью метода NSKeyedUnarchiver unArchiveObjectWithFile:, как показано в программе 19.4. #import
Вывод программы 19.4 abstract class: A class defined so other classes can inherit from it. adopt: To implement all the methods defined in a protocol archiving: Storing an object for later use.
Оператор glossary = [NSKeyedUnarchiver unarchiveObjectWithFile: @"glossary.archive"];
вызывает открытие указанного файла и считывание его содержимого. Файл должен быть создан в результате архивации, для него можно указывать полное или относительное имя, как в данном примере.
После восстановления словаря программа просто выполняет перебор его содержимого, чтобы убедиться, что восстановление было успешным. 19.3. Написание методов кодирования и декодирования
Объекты базовых классов Objective-C, таких как NSString, NSArray, NSDictionary, NSSet, NSDate, NSNumber и NSData, можно архивировать и восстанавливать, как описано в предыдущем разделе. Это относится и к объектам с вложенностью, например, массивам, содержащим строки или другие объекты-массивы.
Мы не можем непосредственно архивировать нашу адресную книгу (AddressBook) с помощью этих средств, поскольку система Objective-C «не знает», как архивировать объект класса AddressBook. Если попытаться архивировать его и программе с помощью строки [N S Keyed Arc h iver archiveRootObject: myAddressBook toFile: @naddrbook.arch"];
то при выполнении программы будет выведено сообщение *** -[AddressBook encodeWithCoder:]: selector not recognized (селектор не распознан) *** Uncaught exception:
Из этих сообщений об ошибках мы видим, что система пыталась найти метод с именем encodeWithCoder: в классе AddressBook, но мы нигде не определяли такой метод.
Чтобы архивировать объекты, отличные от списка в начале раздела, мы должны указать системе, как эти объекты архивировать, или кодировать (encode), а также как их разархивировать, или декодировать (decode). Для этого нужно добавить в определения класса методы encodeWithCoder: и initWithCoder: согласно протоколу . В примере с адресной книгой нужно добавить эти методы в два класса: AddressBook и AddressCard.
Метод encodeWithCoder: вызывается каждый раз, когда архиватору нужно ко-дировать объект из указанного класса, и этот метод указывает ему, как это сделать. Аналогичным образом, метод initWithCoder: вызывается каждый раз, когда нужно декодировать объект указанного класса.
В общем случае метод кодирования должен указывать, как архивировать каждую переменную экземпляра в объекте, который нужно сохранить. Для этого есть вспомогательные средства. Для описанных выше базовых классов Objective– С можно использовать метод encodeObject:forKey:. Для базовых типов данных С (например, делых и с плавающей точкой) используется один из методов, при-веденных в таблице 19.1. Метод декодирования (decoder), initWithCoder:, действует в обратном порядке: мы используем decodeObject:forKey: для декодирования ба-зовых классов Objective-C и подходящий метод декодирования из таблицы 19.1 для базовых типов данных С
Таблица 19.1. Кодирование и декодирование базовых типов данных в архивах с ключами Метод кодирования Метод декодирования encodeBool:forKey: decodeBool:forKey: encodelnt:forKey: decodelnt:forKey: encodelnt32:forKey: decodelnt32:forKey: encodelnt64:forKey: decodelnt64:forKey: encodeFloat:forKey: decodeFloal:forKey: encodeDouble:forKey: decodeDouble:forKey:
В программе 19.5 в классы AddressCard и AddressBook добавлены методы кодирования и декодирования. #import
Следующие два метода, которые используются для класса AddressCard, должны быть добавлены в файл секции implementation. -(void) encodeWithCoder: (NSCoder *) encoder { [encoder encodeObject: name forKey: @"AddressCardName"]; [encoder encodeObject: email forKey: @"AddressCardEmair[; -(id) initWithCoder: (NSCoder *} decoder { name = [[decoder decodeObjectforKey: @"AddressCardName"] retain]; email = [[decoder decodeObjectforKey: @'AddressCardEmail"] retain]; return self; }
Метод кодирования encodeWithCoder: передается объекту NSCoder в качестве его аргумента. Поскольку класс AddressCard наследует непосредственно из NSObject, нам не нужно заботиться о кодировании наследуемых переменных экземпляра. Если суперкласс вашего класса согласуется с протоколом NSCoding, то, чтобы обеспечить кодирование своих наследуемых переменных экземпляра, вы должны начать метод кодирования со строки [super encodeWithCoder: encoder];
Наша адресная книга содержит две переменные экземпляра с именами name н email. Поскольку это объекты класса NSString, мы можем использовать метод encodeObject:forKey: для кодирования каждой из них по порядку. Затем эти переменные экземпляра добавляются в архив.
Метод encodeObject:forKcy: кодирует объект и сохраняет его с указанным ключом для последующего считывания с помощью этого ключа. Имена ключей можно задавать произвольно, для считывания (декодирования) данных нужно использовать тот же ключ, который использовался для их архивации (коди-рования). Конфликт может возникнуть только в том случае, если тот же ключ используется для подкласса кодируемого объекта. Чтобы не возникла эта ситуация, можно вставить имя класса перед именем переменной экземпляра, когда вы составляете ключ для архива, как это сделано в программе 19.5.
Отметим, что encodeObject:forKey: можно использовать для любого объекта, в классе которого имеется соответствующий реализованный метод encodeWithCoder:.
Процесс декодирования действует в обратном порядке. Аргумент, переда-ваемый initWithCoder:, тоже являегся объектом NSCoder. Вам не нужно заботиться об этом ар|ументе; он получает сообщения для каждого объекта, который вы хотите извлечь из архива.
Поскольку в данном случае класс AddressCard наследует непосредственно из NSObject, вам не нужно заботиться о декодировании наследуемых переменных экземпляра. Достаточно вставить следующую строку в начало ваше метода декодирования (decoder), если ваш класс согласуется с протоколом NSCoding. self = [super initWithCoder: decoder];
Каждая переменная экземпляра затем декодируется путем вызова метода decodeObjectforKey: и передачи того же ключа, который использовался для кодирования этой переменной.
Аналогично классу AddressCard, мы добавляем методы кодирования и декодирования в класс AddressBook. В файле секции interface нужно только изменить строку с директивой @interface, чтобы объявить, что теперь с протоколом NSCoding согласуется класс AddressBook. Это изменение может выглядеть следующим образом. @interface AddressBook: NSObject
Ниже определяются методы для включения в файл секции implementation. -(void) encodeWithCoder: (NSCoder *) encoder { (encoder encodeObject: bookName forKey: "AddressBookBookName"]; (encoder encodeObject: book forKey: @"AddressBookBook"]; } -(id) initWithCoder: (NSCoder *} decoder { bookName = [[decoder decodeObjectForKey: @"AddressBookBookName"] retain]; book = [[decoder decodeObjectForKey: @"AddressBookBook"] retain]; return self; }
Программа 19.6 – это тестовая программа. #import «AddressBook. h» #import
Эта программа создает адресную книгу и затем архивирует ее в файле addrbook.arch. При создании архивного файла метод кодирования вызываются из обоих классов: AddressBook и AddressCard. Если вы хотите проверить это, добавьте в методы несколько вызовов NSLog.
В программе 19.7 показано, как считывать архив в память для создания ад-ресной книги из файла. #import "AddressBook.h" #import
Вывод программы 19.7 ======== Contents of: Steve’s Address Book == Jamie Baker [email protected] Julia Kochan [email protected] Stephen Kochan steve@steve_kochan.com Tony lannino [email protected]
При разархивации этой адресной книги автоматически вызываются методы декодирования, добавленные в эти классы. Чтение в программу адресной книги выполняется предельно просто.
Метод encodeObjectforKey: используется применительно к встроенным классам и классам, для которых вы пишете свои методы кодирования и декодирования согласно протоколу NSCoding. Если ваши переменные экземпляра содержат некоторые базовые типы данных, например, int или float, вы должны знать, как кодировать и декодировать их (см. таблицу 19.1).
Ниже приводится простое определение для класса с именем Foo, который содержит три переменные экземпляра: типа NSString, типа int и типа float. Этот класс содержит один метод-установщик, три метода-получателя и два метода кодирования/декодирования, используемых для архивации. @interface Foo: NSObject
Затем следует файл секции implementation. @implementation Foo @synthesize strVal, intVal, floatVal; -(void) encodeWithCoder: (NSCoder *) encoder { [encoder encodeObject: strVal forKey: @"FoostrVarj; [encoder encodelnt: intVal forKey: @"FoointVal"]; [encoder encodeFloat: floatVal forKey: @"Foofk>atVar]; } -(id) initWithCoder: (NSCoder *) decoder { strVal = [[decoder decodeObjectForKey: @"FoostrVal"] retain]; intVal = [decoder decodelntForKey: @"FoointVar]; floatVal = [decoder decodeFloatForKey: @'FoofloatVal"]; return self; } @end
В этой процедуре кодирования сначала кодируется строковое значение strVal с помощью метода encodeObject:forKey:, как показано ранее.
В программе 19.8 создается объект Foo, выполняется его архивация в файл, разархивация и последующий вывод. #import
Вывод программы 19.8 This is the string (Это строка) 12345 98.6
Архивация трех пробных экземпляра из объекта выполняется с помощью следующих сообщений. [encoder encodeObject: strVal forKey: @"FoostrVar]; [encoder encodelnt: intVal forKey: @"FoointVal“]; [encoder encodeRoat: floatVal forKey: @"FoofloatVa!''];
Некоторые из базовых типов данных, такие как char, short, long и long long, не включены в таблицу 19.1; для них необходимо определить размер объекта дан-ных и использовать соответствующую процедуру. Например, тип short int обыч-но имеет размер 16 битов, int и long – 32 или 64 бита, и long long – 64 бита. (Размер любого типа данных определяется с помощью оператора sizeof, см. главу 13.) Например, данные типа short int нужно сохранить их сначала как тип int и затем архивировать с помощью encodeintforKey:. Для декодирования нужно использовать обратный процесс: применить decodelnt:forKey: и затем присвоить результат переменной типа short int. 19.4. Использование NSData для создания нестандартных архивов
Возможно, вы не хотите записывать объект непосредственно в файл с помощью метода archiveRootObject:ToFile:, как в предыдущих примерах. Например, вам нужно собрать некоторые объекты и сохранить их в одном архивном файле. Это можно сделать с помощью обобщенного класса объектов потока данных (data stream) NSData.
Как говорилось в главе 16, объект класса NSData можно использовать для ре-зервирования области памяти для сохранения данных. Эту область памяти можно использовать, например, для временного хранения данных, которые будут последовательно записываться в файл, или для хранения содержимого файла, считанного с диска. Проще всего создать мутабельную область данных с помощью метода data. dataArea = [NSMutableData data];
В результате создается пустое буферное пространство, размер которого расширяется по мере выполнения программы.
В качестве простого примера предположим, что нужно архивировать в од-ном файле адресную книгу и один из объектов класса Foo. Предположим также, что мы добавили методы архивации с ключами в классы AddressBook и AddressCard (см. программу 19.9). #import
После выделения памяти для объекта NSKeyedArchiver передается сообщение initForWritingWithMutableData:, чтобы указать область, в которую будут записываться архивируемые данные; это область NSMutabledata с именем dataArea, которая была создана выше. Сохраненному в архиваторе объекту NSKeyedArchiver можно передавать теперь сообщения кодирования для архивации объектов в программе. На самом деле, архивация и сохранение все кодируемых сообщений выполняется в указанной области данных, пока не получено сообщение finishEncoding.
В данном случае кодируются два объекта: наша адресная книга и объект класса Foo. Для этих объектов можно использовать encodeObjectiforKey:, поскольку мы ранее реализовали методы кодирования (encoder) и кодирования (decoder) для классов AddressBook, AddressCard и Foo.
Закончив архивацию этих объектов, мы передаем объекту archiver сообще-ние finishEncoding. После этого нельзя кодировать никакие объекты, и мы должны передать это сообщение, чтобы завершить процесс архивации.
Область с именем dataArea теперь содержит наши архивированные объекты в форме, которую можно записать в файл. В выражении с сообщением [dataArea writeToFile: @"myArchive" atomically: YES encoding: NSUTF8Encoding error: nil]
передается сообщение writeToFtle:atomically:encoding:error: потоку данных для записи его данных в указанный файл с именем myArchive.
Как видно из части с оператором if, метод writeToFi(e:atomicalfy:encoding:error: возвращает значение YES типа BOOL, если операция записи успешно выполнена, и значение N0, если ее не удалось выполнить (например, был указан неверный путь к файлу или переполнена файловая система).
Восстановление данных из архивного файла осуществляется просто: нужно выполнить все действия в обратном порядке. Во-первых, нужно выделить, как и раньше, область данных, затем в эту область данных прочитать архивный файл. После этого мы создаем объект NSKeyedUnarchiver и сообщаем ему, что требуется декодировать данные из указанной области. Для извлечения и декодирования архивированных объектов нужно вызвать методы декодирования, а по окончании – передать сообщение finishDecoding объекту NSKeyedUnarchiver. Все это выполняется в программе 19. К). #import
Вывод программы 19.10 ======== Contents of: Steve's Address Book – Jamie Baker [email protected] Julia Kochan [email protected] Stephen Kochan steve@steve_kochan.com Tony lannino [email protected] This is the string 12345 98.6
Адресная книга и объект Foo были успешно восстановлены из архивного файла. 19.5. Использование архиватора для копирования объектов
В программе 18.2 мы пытались создать копию массива, содержащего мутабель– ные строковые элементы, и создали поверхностную (shallow) копию этого мас-сива – копировались не сами строки, а только ссылки на них.
Возможности архивации Foundation позволяют создать глубокую (deep) ко-пию объекта. Например, в программе 19.11 выполняется копирование dataAnay в dataArray2 путем архивации dataAnay в буфер и его последующей деархивании с присваиванием результата массиву dataArray2. Нам не нужно задействовать файл для этого процесса; архивацию и деархивацию можно выполнять в памяти. #import
Вывод программы 19.11 dataArray: one two three dataArray2: oneONE two three
Вывод подтверждает, что изменение первого элемента dataArray2 не оказывает влияния на первый элемент dataArray, поскольку новая копия этой строки была получена н ходе архивапии/деархивации.
Операция копирования в программе 19.11 выполняется с помощью следующих двух строк. data – [NSKcycdArchiver archivedDataWithRootObject: dataArray]; dataArray2 = [NSKeyedUnarchiver unarchiveObjectWithData: data];
Мы можем избежать промежуточного присваивания и выполнить копиро-вание с помощью одного оператора. dataArray2 = [NSKeyedUnarchiver unarchiveObjectWithData; [NSKeyedArchiver archivedDataWithRootObject: dataArray]];
Этот подход полезен для создания глубокой копии объекта или объекта, который не поддерживает протокол NSCopying. Упражнения
В главе 15 в программе 15.7 была создана таблица простых чисел. Внесите изменения в эту программу, чтобы записать результирующий массив в виде списка свойств ХМ L в файл primes.pl. Проверьте содержимое этого файла.
Напишите программу для чтения списка свойств XML, созданного в упражнении 1, и сохраните эти свойства в объекте-массиве. Выполните вывод всех элементов массива, чтобы убедиться, что операция восстановления прошла успешно.