ПРОГРАММИРОВАНИЕ ПОД IPHONE, IPAD OBJECTIVE-C часть 3

Сохранение данных в файлы

В предыдущем примере мы рассматривали способы чтения данных с файлов, сохраненных в папке проекта (в песочнице). Но о записи данных, о сохранении данных в файлы, не было сказано ни слова, кроме того, что хранить изменяемые файлы можно только в папке документов (Documents). И это действительно так. Как и в случае с «песочницей», в первую очередь нужно получить путь к папке документов. Обычно я это делаю с помощью дэфайнов:

<code data-result="[object Object]">#define DOCUMENTS [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]</code>

 

Теперь, во все методы, в которые нужно передать путь к папке документов просто подставляйте слово DOCUMENTS.
Поскольку этот пример является продолжением Знакомство с файловой системой iOS — я предлагаю не создавать новый проект, а использовать уже имеющийся.

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

 

В методе viewDidLoad класса FileSystemiOSViewController мы будем сохранять объекты в папку документов. А для их чтения создадим отдельный метод, который назовем readFiles и свяжем его с любой кнопкой интерфейса (кнопку нужно добавить самостоятельно). У меня файл FileSystemiOSViewController.xib выглядит так:

 

Теперь изменим метод viewDidLoad. Как я уже говорил, в нем мы будем копировать файлы с «песочницы» в папку документов:

<code data-result="[object Object]">- (void)viewDidLoad
{
    [super viewDidLoad];

    NSString *filePathBundleXML = [[NSBundle mainBundle] pathForResource:@"menu"ofType:@"xml"]; 
    NSData *xmlData = [NSData dataWithContentsOfFile:filePathBundleXML];

    NSString *filePathDocXML = [DOCUMENTS stringByAppendingPathComponent:@"menu.xml"];
	[xmlData writeToFile:filePathDocXML atomically:YES];
}</code>

 

Первые две строчки вам уже знакомы с примера Подробней о парсинге. В третьей строке мы создаем строку, она будет ссылкой на наш файл. Эта строка состоит с директории папки документов и имени файла, который мы собираемся сохранять. Затем данные, которые мы получили с файла в «песочнице» сохраняем в нашу директорию с помощью метода writeToFile. В качестве параметров этого метода мы передаем толькочто сформированный путь этого файла и логическое значение (атомарно будет сохранятся файл или нет). Подробнее о атомарных операциях можно почитать на википедии.
В уроке NSUserDefaults (хранение данных) я описывал, где программа хранит файл настроек. Если присмотреться — там же вы увидите папку Documents. На картинке я обвел ее синей линией:

Пока что эта папка пуста, но после первого запуска программы:

То же самое произойдет на реальном устройстве.

Теперь организуем чтение этого файла. Для этого изменим метод readFiles:

<code data-result="[object Object]">- (IBAction)readFiles {
    NSString *filePath = [DOCUMENTS stringByAppendingPathComponent:@"menu.xml"];
	NSData *xmlData = [NSData dataWithContentsOfFile:filePath];
    NSString *result = [[NSString alloc] initWithData:xmlData encoding:NSUTF8StringEncoding];
    NSLog(@"%@",result);
}</code>

 

Как и в случае с «песочницей» мы получаем путь к нужному нам файлу и передаем его в качестве параметра в метод dataWithContentsOfFile класса NSData. То есть, метод чтения данных с «песочницы» отличается от метода чтения данных с папки документов лишь путем к файлу. В случае с «песочницей» это:

<code data-result="[object Object]">NSString *filePath = [[NSBundle mainBundle] pathForResource:@"menu"ofType:@"xml"];</code>

 

А с документами:

<code data-result="[object Object]">NSString *filePath = [DOCUMENTS stringByAppendingPathComponent:@"menu.xml"];</code>

 

Но не стоит забывать, о созданном ранее дэфайне DOCUMENTS. Без которого вышеописанный метод не будет работать.

Две последние строки в методе readFiles преобразуют данные в строку и выводит эту строку в консоль. Теперь при нажатии на кнопку добавленную ранее в консоле мы увидим содержимое файла menu.xml:

 

2011-09-07 17:51:51.363 FileSystemiOS[5164:b303] <?xml version=»1.0″?>

<menu>

    

    <dish title=»Green salad with grilled sausage» price=»39.00″>

        <description>with fragrant sausage, Chinese cabbage, bell peppers, tomatoes, tender beans and mushrooms</description>

    </dish>

    

    <dish title=»Salad with bacon and crouton»  price=»38.00″>

        <description>hearty salad with ham, cheese, tomatoes, Chinese cabbage, and toasted croutons in a piquant dressing</description>

    </dish>

    

    <dish title=»Chicken noodle soup»  price=»40.00″>

        <description>fragrant clear soup with chicken and noodles</description>

    </dish>

    

    <dish title=»Hunter’s salad with the language» price=»32.00″>

        <description>beef tongue with two kinds of peppers, lettuce and sweet Crimean onion with a spicy sauce</description>

    </dish>

    

    <dish title=»Salad of pink shrimp»  price=»48.00″>

        <description>and calamari in a spicy garlic sauce, fresh cucumber, lettuce and juicy tomatoes Cherry</description>

    </dish>

 

</menu>
На картинке показана часть файла.

Для графических файлов действуют те же правила. Но картинку мы не будем выводить в консоль. Для ее отображения добавим UIImageView в интерфейс нашей программы. Мы это уже делали в примере Закачка данных. Теперь снова изменим метод viewDidLoad:

<code data-result="[object Object]">- (void)viewDidLoad
{
    [super viewDidLoad];

    NSString *filePathBundleXML = [[NSBundle mainBundle] pathForResource:@"menu"ofType:@"xml"]; 
    NSData *xmlData = [NSData dataWithContentsOfFile:filePathBundleXML];

    NSString *filePathDocXML = [DOCUMENTS stringByAppendingPathComponent:@"menu.xml"];
    [xmlData writeToFile:filePathDocXML atomically:YES];

    NSString *filePathBundleImg = [[NSBundle mainBundle] pathForResource:@"logo"ofType:@"png"]; 
    NSData *imgData = [NSData dataWithContentsOfFile:filePathBundleImg];

    NSString *filePathDocImg = [DOCUMENTS stringByAppendingPathComponent:@"logo.png"];
    [imgData writeToFile:filePathDocImg atomically:YES];
}</code>

 

Обращаю ваше внимание на то, что я не только добавил четыре строки, отвечающие за сохранение картинки в папку документы, но и изменил имена переменных созданных ранее для работы с *.xml-файлом.

Сохранение графического файла аналогично этой же процедуре с текстовым файлом. Разница лишь в имени файла. Осталось организовать вывод картинки на экран устройства:

<code data-result="[object Object]">- (IBAction)readFiles {
    NSString *filePath = [DOCUMENTS stringByAppendingPathComponent:@"menu.xml"];
    NSData *xmlData = [NSData dataWithContentsOfFile:filePath];
    NSString *result = [[NSString alloc] initWithData:xmlData encoding:NSUTF8StringEncoding];
    NSLog(@"%@",result);

    NSString *filePathImg = [DOCUMENTS stringByAppendingPathComponent:@"logo.png"];
    UIImage *image = [UIImage imageWithContentsOfFile:filePathImg];
    imageView.image = image;
}</code>

 

Если UIImageView был добавлен в интерфейс так же как в примере Закачка данных — результат выполнения программы должен быть таким:

 

Теперь продолжим работу с *.plist-файлами, а точнее с файлом array.plist. Как и в предыдущих случаях, в методе viewDidLoadорганизуем копирование файла array.plist с «песочницы» в папку документов:

<code data-result="[object Object]">- (void)viewDidLoad
{
    [super viewDidLoad];

    NSString *filePathBundleXML = [[NSBundle mainBundle] pathForResource:@"menu"ofType:@"xml"]; 
    NSData *xmlData = [NSData dataWithContentsOfFile:filePathBundleXML];

    NSString *filePathDocXML = [DOCUMENTS stringByAppendingPathComponent:@"menu.xml"];
    [xmlData writeToFile:filePathDocXML atomically:YES];

    NSString *filePathBundleImg = [[NSBundle mainBundle] pathForResource:@"logo"ofType:@"png"]; 
    NSData *imgData = [NSData dataWithContentsOfFile:filePathBundleImg];

    NSString *filePathDocImg = [DOCUMENTS stringByAppendingPathComponent:@"logo.png"];
    [imgData writeToFile:filePathDocImg atomically:YES];

    NSString *filePathDocArray = [DOCUMENTS stringByAppendingPathComponent:@"array.plist"];
    NSString *filePathBundleArray = [[NSBundle mainBundle] pathForResource:@"array" ofType:@"plist"];
    [[NSFileManager defaultManager] copyItemAtPath:filePathBundleArray toPath:filePathDocArray error:nil];
}</code>

Последние три строчки отвечают за копирование нужного нам файла. С помощью [NSFileManager defaultManager] мы обращаемся к файловому менеджеру iOS. У этого файлового менеджера есть свои методы работы с файлами. Одним из этих методов является copyItemAtPath. В качестве параметров мы передаем этому методу пути к файлам. Путь к исходному файлу и к существующему (куда будет скопирован этот файл). Следует обратить внимание на то, что путь должен включать имя файла.

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

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

<code data-result="[object Object]">- (IBAction)readFiles {
    NSString *filePath = [DOCUMENTS stringByAppendingPathComponent:@"menu.xml"];
	NSData *xmlData = [NSData dataWithContentsOfFile:filePath];
    NSString *result = [[NSString alloc] initWithData:xmlData encoding:NSUTF8StringEncoding];
    NSLog(@"%@",result);

    NSString *filePathImg = [DOCUMENTS stringByAppendingPathComponent:@"logo.png"];
    UIImage *image = [UIImage imageWithContentsOfFile:filePathImg];
    imageView.image = image;

    NSString *filePathDocArray = [DOCUMENTS stringByAppendingPathComponent:@"array.plist"];
    NSMutableArray *array = [NSMutableArray arrayWithContentsOfFile:filePathDocArray];
    NSLog(@"%@", array);
}</code>

 

Все хорошо, но я не продемонстрировал возможность изменения массива. Для этих целей я предлагаю добавить еще один методchangeFiles, который будет изменять массив. И кнопку, при нажатии на которую будет вызываться этот метод. Теперь файл FileSystemiOSViewController.xib в моем проекте выглядит так:

А сам метод changeFiles:

<code data-result="[object Object]">- (IBAction)changeFiles {
    NSString *filePathDocArray = [DOCUMENTS stringByAppendingPathComponent:@"array.plist"];
    NSMutableArray *array = [NSMutableArray arrayWithContentsOfFile:filePathDocArray];
    [array addObject:@"some text"];
    [array writeToFile:filePathDocArray atomically:YES];
}</code>

 

Теперь, каждый раз при нажатии на кнопку change в файл array.plist добавляется строка some text. После трех нажатий на кнопку change содержимое файла array.plist в консоли выглядит так:

 

2011-09-07 18:00:49.328 FileSystemiOS[5222:b303] (

    «Val 1»,

    «Val 2»,

    «Val 3»,

    «some text»,

    «some text»,

    «some text»

)
Если вы закроете и запустите программу снова — результат в консоли не изменится. Это происходит потому, что приложение читает файл не с «песочницы», а с папки документов.

Чтобы проделать ту же операцию с файлом dictionary.plist не нужно вносить много изменений. Для копирования этого файла в папку документов, в метод viewDidLoad я добавил следующий код:

<code data-result="[object Object]">NSString *filePathDocDic = [DOCUMENTS stringByAppendingPathComponent:@"dictionary.plist"];
NSString *filePathBundleDic = [[NSBundle mainBundle] pathForResource:@"dictionary" ofType:@"plist"];
[[NSFileManager defaultManager] copyItemAtPath:filePathBundleDic toPath:filePathDocDic error:nil];</code>

 

Для чтения и вывода его содержимого в консоль в метод readFiles:

<code data-result="[object Object]">NSString *filePathDocDic = [DOCUMENTS stringByAppendingPathComponent:@"dictionary.plist"];
NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithContentsOfFile:filePathDocDic];
NSLog(@"%@", dic);</code>

 

И для изменения — код в метод changeFiles:

<code data-result="[object Object]">NSString *filePathDocDic = [DOCUMENTS stringByAppendingPathComponent:@"dictionary.plist"];
NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithContentsOfFile:filePathDocDic];
[dic setObject:@"some text" forKey:@"someKey"];
[dic writeToFile:filePathDocDic atomically:YES];</code>

 

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

Comments are closed.