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

Работа с каталогами

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

В примере Сохранение данных в файлы мы научились сохранять различные данные в файлы. Но как быть, если перед сохранением нам нужно проверить, существует ли уже такой файл? Ведь нет смысла лишний раз перезаписывать данные. Более того, если данные в папке документов были изменены пользователем, а в момент очередного запуска программа их заменит исходными данными с песочницы. Это же будет не правильно. Для таких случаев существует метод fileExistsAtPath. Последний раз файлы array.plist мы сохраняли в папку документов вот так:

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

 

Теперь добавим проверку существования файла:

<code data-result="[object Object]">NSString *filePathDocArray = [DOCUMENTS stringByAppendingPathComponent:@"array.plist"];
NSString *filePathBundleArray = [[NSBundle mainBundle] pathForResource:@"array" ofType:@"plist"];

if (![[NSFileManager defaultManager] fileExistsAtPath:filePathDocArray]) {
    [[NSFileManager defaultManager] copyItemAtPath:filePathBundleArray toPath:filePathDocArray error:nil];
    NSLog(@"File saved");
} else {
    NSLog(@"File already exists");
}</code>

 

Вывод информации в консоль не обязателен. Я его добавил лишь для демонстрации. Если выполнить проект первый раз — в консоль будет выведена строка File saved, во все последующие разы — File already exists.

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

<code data-result="[object Object]">NSString *folderPath = [DOCUMENTS stringByAppendingPathComponent:@"MyFolder"];
[[NSFileManager defaultManager] createDirectoryAtPath:folderPath 
                          withIntermediateDirectories:YES 
                                           attributes:nil 
                                                error:nil];</code>

 

Я добавил этот код в метод viewDidLoad класса FileSystemiOSViewController, после его выполнения в каталоге проекта можно увидеть только что созданную папку:

Как найти каталог проекта я уже описывал здесь. С кода видно, что в параметр attributes я передал пустое значение, но в нем можно указать права доступа к этой папке и другие атрибуты.

Теперь, сохраним в только что созданную папку файл dictionary.plist:

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

 

Как видите, вся работа с файловой системой заключается в работе с путями. Практически, ничего нового я не сделал, просто представил в виде строки путь к каталогу в которых хочу сохранить файл и скопировал, пользуясь методом copyItemAtPath.

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

<code data-result="[object Object]">NSDictionary *fileInfo = [[NSFileManager defaultManager] attributesOfItemAtPath:DOCUMENTS error:nil];
NSLog(@"%@", [fileInfo fileType]);</code>

 

Таким способом можено узнать с чем мы имем дело (файл или директория). А если сделать вот так:

<code data-result="[object Object]">NSDictionary *fileInfo = [[NSFileManager defaultManager] attributesOfItemAtPath:DOCUMENTS error:nil];
NSLog(@"%@", fileInfo);</code>

 

В консоль будет выведена полная информация о объекте, ссылку на который вы указали в параметре метода attributesOfItemAtPath:

 

2011-09-08 10:01:29.583 FileSystemiOS[1523:b303] {

    NSFileCreationDate = «2011-09-08 07:01:25 +0000»;

    NSFileExtensionHidden = 0;

    NSFileGroupOwnerAccountID = 20;

    NSFileGroupOwnerAccountName = staff;

    NSFileModificationDate = «2011-09-08 07:01:29 +0000»;

    NSFileOwnerAccountID = 501;

    NSFileOwnerAccountName = Alximik;

    NSFilePosixPermissions = 493;

    NSFileReferenceCount = 7;

    NSFileSize = 238;

    NSFileSystemFileNumber = 1411445;

    NSFileSystemNumber = 234881026;

    NSFileType = NSFileTypeDirectory;

}

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

Метод для перемещения очень похож на метод копирования, отличается лишь названием moveItemAtPath:

<code data-result="[object Object]">NSString *filePathDocArrayFolder = [DOCUMENTS stringByAppendingPathComponent:@"MyFolder/"];
filePathDocArrayFolder = [filePathDocArrayFolder stringByAppendingPathComponent:@"array.plist"];
[[NSFileManager defaultManager] moveItemAtPath:filePathDocArray toPath:filePathDocArrayFolder error:nil];</code>

 

А для удаления объекта просто передайте методу removeItemAtPath путь к нужному объекту:

<code data-result="[object Object]">[[NSFileManager defaultManager] removeItemAtPath:filePathDocDicFolder error:nil];</code>

 

Сложнее обстоят дела с переименовыванием. Дело в том, что у NSFileManager отсутствует метод rename или что-то подобное. Для реализации такой задачи нам поможет уже знакомый moveItemAtPath: Следующий отрывок кода переименует файл array.plist (который находится в папке MyFolder) в newArray.plist.

 

<code data-result="[object Object]">NSString *newName = [filePathDocArrayFolder stringByDeletingLastPathComponent];
newName = [newName stringByAppendingPathComponent:@"newArray.plist"];
[[NSFileManager defaultManager] moveItemAtPath:filePathDocArrayFolder toPath:newName error:nil];</code>

 

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

 

<code data-result="[object Object]">NSString *oldPath = [DOCUMENTS stringByAppendingPathComponent:@"MyFolder/"];
NSString *newPath = [DOCUMENTS stringByAppendingPathComponent:@"NewFolder/"];

NSArray *contentInOldPath = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:oldPath error:nil];

[[NSFileManager defaultManager] createDirectoryAtPath:newPath 
                          withIntermediateDirectories:YES 
                                           attributes:nil 
                                                error:nil];

for (int i = 0; i &lt; contentInOldPath.count; i++) {
    NSString *newFilePath = [newPath stringByAppendingPathComponent:[contentInOldPath objectAtIndex:i]];
    NSString *oldFilePath = [oldPath stringByAppendingPathComponent:[contentInOldPath objectAtIndex:i]];
    [[NSFileManager defaultManager] moveItemAtPath:oldFilePath toPath:newFilePath error:nil];
}

[[NSFileManager defaultManager] removeItemAtPath:oldPath error:nil];</code>

 

Недавно одно из моих приложений не пропустили в AppStore. Причина запрета была следующей:

 

2.23 Apps must follow the iOS Data Storage Guidelines or they will be rejected

 

С чем это может быть связано? Дело в том, что в iOS5 появилась возможность использовать сервис iCloud. Я не буду расписывать, что это такое, скрее всего, что вы и так прекрасно осведомлены. Но вот о том как это работает — не все знают. Дело в том, что при синхронизации устройства с активированной учетной записью этого сервиса все файлы с папка Documents будут помещены в резервную копию iCloud. И в случае, если ваше приложение скачивает большие объемы данных (в моем случае это были видеофайлы), а сохраняет их, соответственно, в вышеупомянутую папку — все эти файлы попадут в резервную копию iCloud. А значит, что места для резервирования других данных может не хватить. Поэтому Apple советую создавать в папке документов отдельные каталоги и устанавливать им атрибуты (маркеры), которые будут отменять их резервное копирование.

 

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

 

<code data-result="[object Object]">- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL*)URL {
    const char* filePath = [[URL path] fileSystemRepresentation];

    const char* attrName = "com.apple.MobileBackup";
    u_int8_t attrValue = 1;

    int result = setxattr(filePath, attrName, &amp;attrValue, sizeof(attrValue), 0, 0);
    return result == 0;
}</code>

 

Чтобы компилятор не выдавал варнингов следует импортировать класс xattr.h (#import <sys/xattr.h>). Теперь внесем изменения в метод создания папки MyFolder:

 

<code data-result="[object Object]">NSString *folderPath = [DOCUMENTS stringByAppendingPathComponent:@"MyFolder"];
[[NSFileManager defaultManager] createDirectoryAtPath:folderPath 
                          withIntermediateDirectories:YES 
                                           attributes:nil 
                                                error:nil];

NSURL *pathURL= [NSURL fileURLWithPath:folderPath];
[self addSkipBackupAttributeToItemAtURL:pathURL];</code>

 

Сам метод мы не изменяли, но после него создали объект, в котором харнится ссылка на папку MyFolder, а замет передали эту ссылку в качестве параметра передали методу addSkipBackupAttributeToItemAtURL, который добавит необходимые атрибуты. Теперь в этот каталог можно сохранять файлы любых размеров и они не будут попадать в резервную копию iCloud.

 

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

Comments are closed.