iOS. Приемы программирования Нахавандипур Вандад
До сих пор мы применяли для сохранения содержимого на диске высокоуровневые классы, например NSString и NSArray. А что, если потребуется сохранить необработанный массив байтов? Это тоже делается просто. Предположим, у нас есть массив из четырех символов и его требуется сохранить на диск:
char bytes[4] = {'a', 'b', 'c', 'd'};
Чтобы сохранить этот необработанный массив байтов на диске простейшим способом, достаточно инкапсулировать его в другой высокоуровневой структуре данных, например NSData, а потом пользоваться соответствующими методами NSData для считывания данных с диска и записи их на диск. Методы сохранения и загрузки данных, применяемые в классе NSData, практически идентичны соответствующим методам классов NSArray и NSDictionary. Вот пример сохранения необработанных данных на диске и считывания их с диска:
NSString *filePath = [NSTemporaryDirectory()
stringByAppendingPathComponent:@"MyFile.txt"];
char bytes[4] = {'a', 'b', 'c', 'd'};
NSData *dataFromBytes = [[NSData alloc] initWithBytes: bytes
length: sizeof(bytes)];
if ([dataFromBytes writeToFile: filePath atomically: YES]){
NSData *readData = [[NSData alloc] initWithContentsOfFile: filePath];
if ([readData isEqualToData: dataFromBytes]){
NSLog(@"The data read is the same data as was written to disk.");
} else {
NSLog(@"Failed to read the data from disk.");
}
} else {
NSLog(@"Failed to save the data to disk.");
}
См. также
Раздел 12.0.
12.3. Создание каталогов на диске
Постановка задачи
Требуется возможность создавать на диске каталоги и сохранять в них определенные файлы из вашего приложения.
Решение
Пользуйтесь методом экземпляра createDirectoryAtPath: withIntermediateDirectories: attributes: error:, относящимся к классу NSFileManager, как показано далее:
— (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSString *tempDir = NSTemporaryDirectory();
NSString *isDir = [tempDir stringByAppendingPathComponent:@"is"];
NSError *error = nil;
if ([fileManager createDirectoryAtPath: isDir
withIntermediateDirectories: YES
attributes: nil
error:&error]){
NSLog(@"Successfully created the directory.");
} else {
NSLog(@"Failed to create the directory. Error = %@", error);
}
self.window = [[UIWindow alloc]
initWithFrame: [[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
Обсуждение
API, предоставляемые классом NSFileManager, очень просты в использовании. Ничего удивительного в том, что для сохранения каталогов на диске эти API требуют написания всего нескольких строк кода. На первый взгляд метод createDirectoryAtPath: withIntermediateDirectories: attributes: error: может показаться страшноватым, но на самом деле не все так плохо. В дальнейшем я расскажу о различных параметрах, которые можно передать этому методу:
• createDirectoryAtPath — путь к тому каталогу, который требуется создать;
• withIntermediateDirectories — логический параметр. Если он имеет значение YES, то перед созданием конечного каталога метод создаст и все промежуточные каталоги. Например, если вы хотите создать каталог is, вложенный в каталог data, который, в свою очередь, вложен в каталог tmp, а каталог data пока не создан, то можете приказать этому методу создать каталог tmp/data/is/, установив для параметра withIntermediateDirectories значение YES. В таком случае система создаст и каталог data, и каталог is;
• attributes — словарь атрибутов, который можно передать системе. Эти атрибуты будут определять детали создания каталога. Здесь мы не будем использовать этот параметр, чтобы не усложнять пример. Но тут вы можете изменять такую информацию, как дата и время внесения изменений, дата и время создания, а также при желании и другие атрибуты созданного каталога;
• error — данный параметр принимает указатель на объект-ошибку типа NSObject. Этот объект будет заполняться любыми ошибками, которые могут возникать в процессе создания каталога. В принципе, целесообразно передавать объект-ошибку в этом параметре, так что, если он завершится неудачно (возвратит NO), вы сможете обратиться к ошибке и определить, что именно пошло не так.
См. также
Раздел 12.1.
12.4. Перечисление файлов и каталогов
Постановка задачи
Вы хотите построить перечень подкаталогов, содержащихся в каталоге, либо построить список файлов, содержащихся в каталоге. Акт перечисления означает, что вы просто хотите найти все каталоги и/или файлы, расположенные внутри другого каталога.
Решение
Используйте метод экземпляра contentsOfDirectoryAtPath: error:, относящийся к классу NSFileManager, как показано далее. В данном примере мы перечисляем все файлы, каталоги и символьные ссылки, расположенные в каталоге пакета с приложением:
— (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSString *bundleDir = [[NSBundle mainBundle] bundlePath];
NSError *error = nil;
NSArray *bundleContents = [fileManager
contentsOfDirectoryAtPath: bundleDir
error:&error];
if ([bundleContents count] > 0 &&
error == nil){
NSLog(@"Contents of the app bundle = %@", bundleContents);
}
else if ([bundleContents count] == 0 &&
error == nil){
NSLog(@"Call the police! The app bundle is empty.");
}
else {
NSLog(@"An error happened = %@", error);
}
self.window = [[UIWindow alloc]
initWithFrame: [[UIScreen mainScreen] bounds]];
// Точка переопределения для дополнительной настройки после запуска приложения
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
Обсуждение
В некоторых приложениях для iOS иногда требуется строить перечень содержимого каталога. Возможно, вы пока не вполне понимаете, зачем это может понадобиться, поэтому рассмотрим соответствующий пример. Допустим, пользователь хочет скачать из Интернета 10 изображений и кэшировать их в вашем приложении. Вы выполняете эту операцию и сохраняете их, допустим, в каталоге tmp/is/, который создали вручную. Затем пользователь закрывает ваше приложение и вновь открывает его, а вы хотите отобразить в пользовательском интерфейсе вашей программы список уже загруженных файлов-изображений (в табличном виде). Как это сделать? Ничего сложного. Вам всего лишь потребуется перечислить содержимое вышеупомянутого каталога с помощью класса NSFileManager. Как было показано в подразделе «Решение» данного раздела, метод экземпляра contentsOfDirectoryAtPath: error:, относящийся к классу NSFileManager, возвращает массив объектов NSString, которые и соответствуют файлам, подкаталогам и символьным ссылкам внутри заданного каталога. Тем не менее непросто определить, какой из этих объектов является файлом, какой — подкаталогом и т. д. Чтобы получить от файлового менеджера более детализированную информацию, вызовите метод contentsOfDirec toryAtURL: includingPropertiesForKeys: options: error:. Рассмотрим параметры, которые можно передавать этому методу.
• contentsOfDirectoryAtURL — путь к каталогу, который вы хотите просмотреть. Этот путь должен предоставляться как экземпляр NSURL. Не волнуйтесь, если не знаете, как построить этот экземпляр. Вскоре мы об этом поговорим.
• includingPropertiesForKeys — это массив свойств, которые система iOS должна выбирать для каждого файла, каталога или элемента, найденного в конкретной директории. Например, вы можете указать, что в результатах должна возвращаться дата создания каждого элемента. Эта информация должна возвращаться в составе приходящего к вам экземпляра URL (или в экземплярах NSURL, получаемых от фреймворка). Вот список некоторых наиболее важных значений, которые могут находиться в этом массиве:
• NSURLIsDirectoryKey — позволяет постфактум определить, указывает ли один из возвращенных URL на каталог;
• NSURLIsReadableKey — возвращает дату создания того элемента, который расположен по возвращенному URL;
• NSURLContentAccessDateKey — в возвращаемых результатах передает дату последнего обращения к содержимому;
• NSURLContentModificationDateKey — как понятно из названия, это значение позволяет определять дату последнего изменения информации, расположенной по возвращенному URL.
• options — для этого параметра можно передать только одно из двух значений: 0 или NSDirectoryEnumerationSkipsHiddenFiles. Если введено второе значение, то, как понятно из его названия, при построении перечня будут пропущены все скрытые элементы.
• error — ссылка на объект, в который будет записываться информация об ошибке, если методу не удастся выполнить стоящую перед ним задачу. Обычно целесообразно передавать этому методу объекты-ошибки, если есть такая возможность. Если какие-то ошибки и будут возникать, то такие объекты помогут вам более уверенно с ними справляться.
Теперь, когда мы значительно более полно контролируем перечисление элементов, построим перечень всех элементов из каталога. app и выведем даты создания, последнего изменения элемента и последнего обращения к нему. Кроме того, будем выводить информацию о том, является ли этот элемент скрытым, а также есть ли у нас право считывания конкретного файла. Наконец, мы также укажем, являются конкретные элементы каталогами или нет. Приступим:
— (NSArray *) contentsOfAppBundle{
NSFileManager *manager = [[NSFileManager alloc] init];
NSURL *bundleDir = [[NSBundle mainBundle] bundleURL];
NSArray *propertiesToGet = @[
NSURLIsDirectoryKey,
NSURLIsReadableKey,
NSURLCreationDateKey,
NSURLContentAccessDateKey,
NSURLContentModificationDateKey
];
NSError *error = nil;
NSArray *result = [manager contentsOfDirectoryAtURL: bundleDir
includingPropertiesForKeys: propertiesToGet
options:0
error:&error];
if (error!= nil){
NSLog(@"An error happened = %@", error);
}
return result;
}
— (NSString *) stringValueOfBoolProperty:(NSString *)paramProperty
ofURL:(NSURL *)paramURL{
NSNumber *boolValue = nil;
NSError *error = nil;
[paramURL getResourceValue:&boolValue
forKey: paramProperty
error:&error];
if (error!= nil){
NSLog(@"Failed to get property of URL. Error = %@", error);
}
return [boolValue isEqualToNumber:@YES]? @"Yes": @"No";
}
— (NSString *) isURLDirectory:(NSURL *)paramURL{
return [self stringValueOfBoolProperty: NSURLIsDirectoryKey ofURL: paramURL];
}
— (NSString *) isURLReadable:(NSURL *)paramURL{
return [self stringValueOfBoolProperty: NSURLIsReadableKey ofURL: paramURL];
}
— (NSDate *) dateOfType:(NSString *)paramType inURL:(NSURL *)paramURL{
NSDate *result = nil;
NSError *error = nil;
[paramURL getResourceValue:&result
forKey: paramType
error:&error];
if (error!= nil){
NSLog(@"Failed to get property of URL. Error = %@", error);
}
return result;
}
— (void) printURLPropertiesToConsole:(NSURL *)paramURL{
NSLog(@"Item name = %@", [paramURL lastPathComponent]);
NSLog(@"Is a Directory? %@", [self isURLDirectory: paramURL]);
NSLog(@"Is Readable? %@", [self isURLReadable: paramURL]);
NSLog(@"Creation Date = %@",
[self dateOfType: NSURLCreationDateKey inURL: paramURL]);
NSLog(@"Access Date = %@",
[self dateOfType: NSURLContentAccessDateKey inURL: paramURL]);
NSLog(@"Modification Date = %@",
[self dateOfType: NSURLContentModificationDateKey inURL: paramURL]);
NSLog(@"—");
}
— (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
NSArray *itemsInAppBundle = [self contentsOfAppBundle];
for (NSURL *item in itemsInAppBundle){
[self printURLPropertiesToConsole: item];
}
self.window = [[UIWindow alloc]
initWithFrame: [[UIScreen mainScreen] bounds]];
// Точка переопределения для дополнительной настройки после запуска приложения
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
Вывод этой программы получится примерно таким:
Item name = Assets.car
Is a Directory? No Is Readable? Yes
Creation Date = 2013-06-25 16:12:53 +0000
Access Date = 2013-06-25 16:12:53 +0000
Modification Date = 2013-06-25 16:12:53 +0000
–
Item name = en.lproj
Is a Directory? Yes
Is Readable? Yes
Creation Date = 2013-06-25 16:12:53 +0000
Access Date = 2013-06-25 16:15:02 +0000
Modification Date = 2013-06-25 16:12:53 +0000
–
Item name = Enumerating Files and Folders
Is a Directory? No Is Readable? Yes
Creation Date = 2013-06-25 16:15:01 +0000
Access Date = 2013-06-25 16:15:04 +0000
Modification Date = 2013-06-25 16:15:01 +0000
–
Говоря об этом приложении, необходимо отметить, что мы используем метод экземпляра getResourceValue: forKey: error:, относящийся к классу NSURL, чтобы получить значение каждого из ключей, запрашиваемых у файлового менеджера, — например, даты создания и последнего изменения элемента. Эти требования мы передаем файловому менеджеру и приказываем ему выбрать эту информацию. Затем, как только у нас будут нужные URL, воспользуемся вышеупомянутым методом для получения различных свойств от результирующих URL.
Итак, рассмотрим различные части приложения. Я просто объясню, что делает каждый из написанных нами методов.
• contentsOfAppBundle — этот метод выполняет поиск в каталоге. app и находит все его элементы (файлы, подкаталоги, символьные ссылки и др.), после чего возвращает результат в виде массива. Все элементы в этом массиве относятся к типу NSURL и содержат дату собственного создания, последнего изменения, а также другие атрибуты, рассмотренные ранее.
• stringValueOfBoolProperty: ofURL: — этот метод выбирает строковый эквивалент (Yes или No) логического свойства URL. Например, информация о том, указывает конкретный URL на каталог или нет, сохраняется как двоичное логическое значение. Однако если вы хотите вывести это логическое значение на консоль, то его нужно преобразовать в строку. Для каждого URL у нас есть два элемента запроса, которые будут возвращать экземпляры NSNumber. Каждый из этих экземпляров (NSURLIsDirectoryKey и NSURLIsReadableKey) содержит логическое значение. Итак, нам не приходится писать этот код для преобразования дважды, поскольку есть специальные методы для преобразования NSNumber в строку Yes или No.
• isURLDirectory: — принимает URL и проверяет, является ли он каталогом. На внутрисистемном уровне этот метод использует метод stringValueOfBoolProperty: ofURL: и передает ему ключ NSURLIsDirectoryKey.
• isURLReadable: — определяет, обладает ли ваше приложение доступом на чтение по указанному URL. На внутрисистемном уровне этот метод также использует метод stringValueOfBoolProperty: ofURL: и передает ему ключ NSURLIsDirectoryKey.
• dateOfType: inURL: — поскольку мы собираемся просматривать у каждого URL, соответствующего NSDate, свойства трех типов, просто инкапсулируем в данный метод нужный для этого код. Метод будет принимать ключ и возвращать в URL дату, ассоциированную с конкретным ключом.
Ну вот и все. Вы научились перечислять каталоги и получать все элементы, расположенные в конкретном каталоге. Вы даже умеете получать различные атрибуты для разных элементов.
См. также
Разделы 12.1 и 12.2.
12.5. Удаление файлов и каталогов