iOS. Приемы программирования Нахавандипур Вандад
}
else if ([jsonObject isKindOfClass: [NSArray class]]){
NSArray *deserializedArray = (NSArray *)jsonObject;
NSLog(@"Deserialized JSON Array = %@", deserializedArray);
}
else {
/* Был возвращен какой-то другой объект. Мы не знаем, что делать
в этой ситуации, так как десериализатор возвращает только словари
или массивы. */
}
}
else if (error!= nil){
NSLog(@"An error happened while deserializing the JSON data.");
}
}
else if ([jsonData length] == 0 &&
error == nil){
NSLog(@"No data was returned after serialization.");
}
else if (error!= nil){
NSLog(@"An error happened = %@", error);
}
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
// Точка переопределения для дополнительной настройки после запуска приложения
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
Параметр options метода JSONObjectWithData: options: error: принимает одно или несколько следующих значений:
• NSJSONReadingMutableContainers — словарь или массив, возвращенный методом JSONObjectWithData: options: error:, будет изменяемым. Иными словами, этот метод будет возвращать либо экземпляр NSMutableArray, либо экземпляр NSMutableDictionary в противоположность изменяемому массиву или словарю;
• NSJSONReadingMutableLeaves — листовые значения будут инкапсулированы в экземпляры NSMutableString;
• NSJSONReadingAllowFragments — обеспечивает десериализацию данных JSON, чей корневой объект верхнего уровня не является массивом или словарем.
См. также
Раздел 11.9.
11.11. Включение в приложения функций социального обмена контентом
Постановка задачи
Требуется предоставить в приложении функции социального обмена контентом. Например, у пользователя мобильного устройства должна быть возможность написать твит или обновить статус в Facebook.
Решение
Внедрите в ваше приложение фреймворк Social и воспользуйтесь классом SLComposeViewController для обеспечения социального обмена сообщениями, например твитами.
Обсуждение
Класс SLComposeViewController входит в состав фреймворка Social. Он приспособлен к работе с модулями компилятора LLVM. Чтобы приступить к использованию этого фреймворка, вам всего лишь потребуется импортировать в проект обобщающий заголовок, вот так:
#import «ViewController.h»
#import <Social/Social.h>
@implementation ViewController
Поскольку Apple обогащает свой SDK новыми возможностями социального обмена контентом, вы можете запрашивать фреймворк Social и прямо во время выполнения узнавать, какой из сервисов доступен на устройстве, на котором работает ваше приложение. Поскольку набор таких сервисов может варьироваться от устройства к устройству, перед попыткой использовать тот или иной сервис обязательно следует убедиться, что нужный сервис работает. Чтобы запросить у iOS такую информацию, воспользуйтесь методом класса isAvailableForServiceType:, относящимся к классу SLComposeViewController. Параметр, передаваемый этому методу, относится к типу NSString, а вот список некоторых валидных параметров, которые можно передать этому методу:
• SOCIAL_EXTERN NSString *const SLServiceTypeTwitter;
• SOCIAL_EXTERN NSString *const SLServiceTypeFacebook;
• SOCIAL_EXTERN NSString *const SLServiceTypeSinaWeibo;
• SOCIAL_EXTERN NSString *const SLServiceTypeTencentWeibo;
• SOCIAL_EXTERN NSString *const SLServiceTypeLinkedIn.
Убедившись, что нужный сервис доступен, вы можете воспользоваться методом класса composeViewControllerForServiceType:, относящимся к классу SLComposeViewController. Так вы получаете новый экземпляр контроллера вида для социального обмена. Далее все совсем просто. Вам потребуется всего лишь использовать в контроллере для социального обмена один или несколько следующих методов:
• setInitialText: — задает строку, которой вы хотите поделиться;
• addImage: — добавляет изображение, которое должно прикрепляться к вашему посту;
• addURL: — добавляет URL, которым можно делиться наряду с текстом и изображением.
Экземпляр класса SLComposeViewController также обладает очень удобным свойством completionHandler. Оно представляет собой блоковый объект типа SLComposeViewControllerCompletionHandler. Этот обработчик завершения будет вызываться всякий раз, когда пользователь успешно завершает процесс обмена контентом (то есть пользователь успешно отправляет пост, который iOS доставляет на сайт Twitter, Facebook и др.) либо закрывает диалоговое окно. Этому методу будет передаваться параметр типа SLComposeViewControllerResult. Он обозначает тип произошедшего события — например, успех или отмену операции.
Итак, довольно слов, переходим к сути. Далее будет рассмотрен фрагмент кода, который пытается определить, обладает ли данное устройство возможностями социального обмена контентом через Twitter. Если это так, код создает простой твит с картинкой и URL, после чего отображает для пользователя диалоговое окно Twitter, готовое к отправке сообщения:
— (void) viewDidAppear:(BOOL)animated{
[super viewDidAppear: animated];
if ([SLComposeViewController
isAvailableForServiceType: SLServiceTypeTwitter]){
SLComposeViewController *controller =
[SLComposeViewController
composeViewControllerForServiceType: SLServiceTypeTwitter];
[controller setInitialText:@"MacBook Airs are amazingly thin!"];
[controller addImage: [UIImage iNamed:@"MacBookAir"]];
[controller addURL: [NSURL URLWithString:@"http://www.apple.com/"]];
controller.completionHandler = ^(SLComposeViewControllerResult result){
NSLog(@"Completed");
};
[self presentViewController: controller animated: YES completion: nil];
} else {
NSLog(@"The twitter service is not available");
}
}
Запустив это приложение на устройстве, где поддерживается работа с Twitter (такая интеграция обеспечивается с помощью соответствующих настроек iOS), вы увидите картинку, напоминающую рис. 11.2.
Рис. 11.2. Создание простого твита с помощью фреймворка Social
Обладая этой информацией, мы можем создавать и разные другие сообщения — например, обновления для учетной записи Facebook. На самом деле, как объяснялось ранее, вам всего лишь потребуется определить во время исполнения, активизирован ли на устройстве искомый сервис, а потом попытаться воспользоваться им — добавить в запросе текст, изображения, URL.
Наконец, не забывайте, что обработчики завершения для ваших видов, используемых для составления таких сообщений, могут вызываться не в том потоке, в котором вы создавали контроллер. Итак, помня об этом, пользуйтесь приемами, изученными в главе 7, и переключайтесь на работу с главным потоком внутри обработчика завершения, если собираетесь выполнять что-либо, имеющее отношение к пользовательскому интерфейсу.
См. также
Раздел 11.0.
11.12. Синтаксический разбор XML с помощью NSXMLParser
Постановка задачи
Необходимо выполнить синтаксический разбор (парсинг) фрагмента кода на языке XML или XML-документа.
Решение
Воспользуйтесь классом NSXMLParser.
Обсуждение
Для синтаксического разбора XML-содержимого класс NSXMLParser использует делегат. Создадим простой XML-файл, содержащий следующие данные (сохраните этот файл в вашем проекте как MyXML.xml):
<?xml version="1.0" encoding="UTF-8"?>
<root>
<person id="1">
<firstName>Anthony</firstName>
<lastName>Robbins</lastName>
<age>51</age>
</person>
<person id="2">
<firstName>Richard</firstName>
<lastName>Branson</lastName>
<age>61</age>
</person>
</root>
Теперь определим свойство типа NSXMLParser:
#import «AppDelegate.h»
@interface AppDelegate () <NSXMLParserDelegate>
@property (nonatomic, strong) NSXMLParser *xmlParser;
@end
@implementation AppDelegate
Кроме того, как видите, я определил делегат моего приложения как делегат XML-парсера, который подчиняется протоколу NSXMLParserDelegate. Согласно этому протоколу, объект делегата XML-парсера должен относиться к типу NSXMLParser. Cчитаем с диска файл MyXML.xml и передадим его на обработку в XML-парсер:
— (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
NSString *xmlFilePath = [[NSBundle mainBundle] pathForResource:@"MyXML"
ofType:@"xml"];
NSData *xml = [[NSData alloc] initWithContentsOfFile: xmlFilePath];
self.xmlParser = [[NSXMLParser alloc] initWithData: xml];
self.xmlParser.delegate = self;
if ([self.xmlParser parse]){
NSLog(@"The XML is parsed.");
} else{
NSLog(@"Failed to parse the XML");
}
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
Сначала считываем содержимое файла в экземпляр NSData, а потом инициализируем XML-парсер с помощью метода initWithData:, используя данные, считанные из XML-файла. Затем вызываем метод parse XML-парсера, чтобы запустить процесс синтаксического разбора. Этот метод заблокирует актуальный поток до тех пор, пока синтаксический разбор не завершится. Если вам требуется произвести синтаксический разбор больших XML-файлов, используйте для этого глобальную диспетчерскую очередь.
Для синтаксического разбора XML-файла необходимо знать методы делегатов, определенные в протоколе NSXMLParserDelegate, а также понимать, за что они отвечают:
• parserDidStartDocument: — вызывается при запуске синтаксического разбора;
• parserDidEndDocument: — вызывается по окончании синтаксического разбора;
• parser: didStartElement: namespaceURI: qualifiedName: attributes: — вызывается, когда парсер встречает и начинает разбирать новый элемент в XML-документе;
• parser: didEndElement: namespaceURI: qualifiedName: — вызывается, когда парсер завершает синтаксический разбор текущего элемента;
• parser: foundCharacters: — вызывается, когда парсер анализирует строковое содержимое элементов.
С помощью этих методов делегата можно определить объектную модель для XML-объектов. Сначала определим объект, который будет представлять XML-элемент. Сделаем это в классе XMLElement:
#import <Foundation/Foundation.h>
@interface XMLElement: NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *text;
@property (nonatomic, strong) NSDictionary *attributes;
@property (nonatomic, strong) NSMutableArray *subElements;
@property (nonatomic, weak) XMLElement *parent;
@end
Теперь реализуем класс XMLElement:
#import «XMLElement.h»
@implementation XMLElement
— (NSMutableArray *) subElements{
if (subElements == nil){
subElements = [[NSMutableArray alloc] init];
}
return subElements;
}
@end
Мы хотим, чтобы изменяемый массив subElements создавался лишь тогда, когда при достижении этой точки в коде мы имеем значение nil. Поэтому код для выделения и инициализации свойства subElements класса XMLElement поместим в его собственном методе-получателе. Если у XML-элемента нет дочерних элементов, то использовать это свойство не придется. Ведь отсутствует точка, в которой можно было бы выделить и инициализировать изменяемый массив для данного элемента. Такая техника называется «ленивое выделение» (Lazy Allocation).
Итак, продолжим. Определим экземпляр XMLElement и назовем его rootElement. Наш план — начать синтаксический разбор и подробно изучить XML-файл по мере разбора его и методов его делегата, пока не рассмотрим весь файл целиком:
#import «AppDelegate.h»
#import «XMLElement.h»
@interface AppDelegate () <NSXMLParserDelegate>
@property (nonatomic, strong) UIWindow *window;
@property (nonatomic, strong) NSXMLParser *xmlParser;
@property (nonatomic, strong) XMLElement *rootElement;
@property (nonatomic, strong) XMLElement *currentElementPointer;
@end
@implementation AppDelegate
currentElementPointer будет соответствовать тому XML-элементу, который мы в данный момент разбираем в XML-структуре. В ходе синтаксического разбора можно будет перемещаться по этой структуре вверх и вниз. В отличие от постоянно изменяющегося указателя currentElementPointer, rootElement всегда будет оставаться корневым элементом XML-файла и его значение не изменится в ходе синтаксического разбора данного файла.
Начнем синтаксический разбор. Первый элемент, который нас интересует, — это метод parserDidStartDocument:. В нем мы просто сбрасываем все значения:
— (void)parserDidStartDocument:(NSXMLParser *)parser{
self.rootElement = nil;
self.currentElementPointer = nil;
}
Следующий метод называется parser: didStartElement: namespaceURI: qualifiedName: attributes:. В этом методе создадим корневой элемент (если он еще не создан). Когда в XML-файле начинается разбор любого нового элемента, мы вычисляем, где именно в структуре XML-файла находимся, а потом добавляем новый элемент-объект к актуальному элементу-объекту:
— (void) parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict{
if (self.rootElement == nil){
/* У нас нет корневого элемента. Создадим такой элемент
и укажем на него. */
self.rootElement = [[XMLElement alloc] init];
self.currentElementPointer = self.rootElement;
} else {
/* Корневой элемент уже есть. Создаем новый элемент и добавляем его
в качестве одного из дочерних элементов текущего элемента. */
XMLElement *newElement = [[XMLElement alloc] init];
newElement.parent = self.currentElementPointer;
[self.currentElementPointer.subElements addObject: newElement];
self.currentElementPointer = newElement;
}
self.currentElementPointer.name = elementName;
self.currentElementPointer.attributes = attributeDict;
}
Теперь перед нами метод parser: foundCharacters:. Для каждого текущего элемента этот метод может вызываться несколько раз, поэтому необходимо гарантировать, что мы сможем сделать несколько записей в этом методе. Например, если текст элемента имеет 4000 символов в длину, то парсер может разобрать не более 1000 символов за первый ход, еще 1000 — за второй и т. д. В таком случае синтаксический анализатор вызовет метод parser: foundCharacters: для данного элемента четыре раза. Вероятно, вам потребуется просто аккумулировать результаты и вернуть их в виде строки:
— (void) parser:(NSXMLParser *)parser
foundCharacters:(NSString *)string{
if ([self.currentElementPointer.text length] > 0){
self.currentElementPointer.text =
[self.currentElementPointer.text stringByAppendingString: string];
} else {
self.currentElementPointer.text = string;
}