iOS. Приемы программирования Нахавандипур Вандад
Щипки позволяют пользователю легко масштабировать (увеличивать и уменьшать) элементы графического интерфейса. Например, браузер Safari в iOS дает возможность щипком на веб-странице увеличивать ее содержимое. Щипок работает в двух направлениях: увеличение и уменьшение масштаба. Это непрерывный жест, который на сенсорном экране всегда выполняется двумя пальцами.
Данный распознаватель жестов может пребывать в следующих состояниях:
• UIGestureRecognizerStateBegan;
• UIGestureRecognizerStateChanged;
• UIGestureRecognizerStateEnded.
Как только щипок распознан, вызывается действующий метод целевого объекта (и будет последовательно вызываться до тех пор, пока щипок не окончится). В действующем методе вы получаете доступ к двум очень важным методам распознавателя щипков: scale и velocity. scale — это коэффициент, на который нужно изменить размер элемента графического интерфейса по осям X и Y, чтобы отразить таким образом размер пользовательского жеста. velocity — это скорость щипка, измеряемая в пикселах в секунду. Скорость имеет отрицательное значение, если пальцы движутся навстречу друг другу, и положительное — если они перемещаются в разные стороны.
Значение свойства scale можно передать функции CGAffineTransformMakeScale из фреймворка Core Graphics, чтобы получить аффинное преобразование. Такое преобразование применимо к свойству transform любого экземпляра класса UIView, оно позволяет изменять преобразование этого элемента. Мы воспользуемся этой функцией следующим образом:
— (void) handlePinches:(UIPinchGestureRecognizer*)paramSender{
if (paramSender.state == UIGestureRecognizerStateEnded){
self.currentScale = paramSender.scale;
} else if (paramSender.state == UIGestureRecognizerStateBegan &&
self.currentScale!= 0.0f){
paramSender.scale = self.currentScale;
}
if (paramSender.scale!= NAN &&
paramSender.scale!= 0.0){
paramSender.view.transform =
CGAffineTransformMakeScale(paramSender.scale,
paramSender.scale);
}
}
Поскольку свойство scale распознавателя жестов сбрасывается всякий раз, когда регистрируется новый щипок, мы сохраняем последнее значение этого свойства в общем свойстве экземпляра (Instance Property) контроллера вида, называемом currentScale. В следующий раз, когда будет распознан новый жест, мы отсчитываем коэффициент масштабирования от последнего зафиксированного значения, что и продемонстрировано в коде.
Глава 11. Сетевые функции, JSON, XML и Twitter
11.0. Введение
Стоит подключить приложение iOS к Интернету — и оно становится гораздо интереснее. Например, представьте себе приложение, которое предлагает пользователям великолепные фоновые картинки для Рабочего стола. Пользователь может выбрать вариант из большого списка изображений и присвоить любой из этих рисунков в качестве фонового операционной системе iOS. А теперь вообразим себе приложение, которое делает то же самое, но обновляет ассортимент имеющихся изображений каждый день, неделю или месяц. Пользователь после какого-то перерыва возвращается к работе с программой и — опа! Масса новых фоновых изображений динамически загружается в приложение. В этом и есть изюминка работы с веб-службами и Интернетом. Реализовать такие функции не составляет труда, если обладать базовыми знаниями о работе в Сети, применении JSON, XML и Twitter. Ну, еще от разработчика приложения требуется известная креативность.
iOS SDK позволяет подключаться к Интернету, получать и отсылать данные. Это делается с помощью класса NSURLConnection. Сериализация и десериализация JSON выполняется в классе NSJSONSerialization. Синтаксический разбор XML производится с помощью NSXMLParser, а соединение с Twitter обеспечивается во фреймворке Twitter.
В SDK iOS 7 появились новые классы, работать с которыми мы научимся в этой главе. В частности, поговорим о классе NSURLSession, который управляет соединяемостью веб-сервисов и решает эту задачу более основательно, чем класс NSURLConnection. О соединяемости мы также поговорим далее в этой главе.
11.1. Асинхронная загрузка с применением NSURLConnection
Постановка задачи
Необходимо асинхронно загрузить файл с имеющегося URL.
Решение
Используйте класс NSURLConnection с асинхронным запросом.
Обсуждение
Класс NSURLConnection можно использовать двумя способами — асинхронным и синхронным. При асинхронном соединении создается новый поток, и процесс загрузки выполняется в этом новом потоке. Синхронное соединение блокирует вызывающий поток, а содержимое загружается прямо в ходе обмена данными.
Многие разработчики полагают, что при синхронном соединении блокируется главный поток, но это неверно. Синхронное соединение всегда блокирует тот поток, в котором оно было инициировано. Если вы запускаете синхронное соединение из главного потока — да, главный поток будет заблокирован. Но синхронное соединение, запущенное из другого потока, будет напоминать асинхронное именно в том отношении, что оно никак не повлияет на главный поток. На самом деле единственное различие между синхронным и асинхронным соединениями заключается в том, что для асинхронного соединения среда времени исполнения создает отдельный поток, а для синхронного — нет.
Чтобы создать асинхронное соединение, необходимо следующее.
1. Иметь URL или экземпляр NSString.
2. Преобразовать строку в экземпляр NSURL.
3. Поместить URL в URL-запросе типа NSURLRequest, а если мы имеем дело с изменяемыми URL — в экземпляр NSMutableURLRequest.
4. Создать экземпляр NSURLConnection и передать ему URL-запрос.
Можно создать асинхронное соединение по URL с помощью метода класса sendAsynchronousRequest: queue: completionHandler:, относящегося к классу NSURLConnection. Этот метод имеет следующие параметры:
• sendAsynchronousRequest — запрос типа NSURLRequest, рассмотренный ранее;
• queue — операционная очередь. При желании можно просто выделить и инициализировать новую операционную очередь и передать ее этому методу;
• completionHandler — блоковый объект, выполняемый, когда асинхронное соединение завершает работу, успешно или неуспешно. Этот блоковый объект должен принимать три параметра:
• объект типа NSURLResponse, в котором заключается ответ, полученный нами от сервера, — при наличии такого ответа;
• данные типа NSData при их наличии. Это будут данные, собранные в ходе соединения по указанному URL;
• ошибка типа NSError в случае ее возникновения.
Метод sendAsynchronousRequest: queue: completionHandler: не вызывается в главном потоке. Поэтому, если вам потребуется решить задачу, связанную с пользовательским интерфейсом, убедитесь, что вернулись к главному потоку.
Итак, довольно теории, перейдем к примерам. В данном примере попытаемся собрать HTML-контент с домашней страницы Apple, а потом выведем эту информацию в строковом формате в окне консоли:
NSString *urlAsString = @"http://www.apple.com";
NSURL *url = [NSURL URLWithString: urlAsString];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL: url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection
sendAsynchronousRequest: urlRequest
queue: queue
completionHandler: ^(NSURLResponse *response,
NSData *data,
NSError *error) {
if ([data length] >0 &&
error == nil){
NSString *html = [[NSString alloc] initWithData: data
encoding: NSUTF8StringEncoding];
NSLog(@"HTML = %@", html);
}
else if ([data length] == 0 &&
error == nil){
NSLog(@"Nothing was downloaded.");
}
else if (error!= nil){
NSLog(@"Error happened = %@", error);
}
}];
Да, все так просто. Если вы хотите сохранить данные, которые мы загрузили на диск в ходе соединения, это можно сделать с помощью подходящих методов класса NSData, получаемых от завершающего блока:
NSString *urlAsString = @"http://www.apple.com";
NSURL *url = [NSURL URLWithString: urlAsString];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL: url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection
sendAsynchronousRequest: urlRequest
queue: queue
completionHandler: ^(NSURLResponse *response,
NSData *data,
NSError *error) {
if ([data length] >0 &&
error == nil){
/* Прикрепляем имя файла к каталогу с документами. */
NSURL *filePath =
[[self documentsFolderUrl]
URLByAppendingPathComponent:@"apple.html"];
[data writeToURL: filePath atomically: YES];
NSLog(@"Successfully saved the file to %@", filePath);
}
else if ([data length] == 0 &&
error == nil){
NSLog(@"Nothing was downloaded.");
}
else if (error!= nil){
NSLog(@"Error happened = %@", error);
}
}];
Все действительно просто. В более ранних версиях iOS SDK соединения по URL происходили с применением делегирования, но теперь модель стала обычной блоковой и вам не придется заниматься реализацией делегатов.
11.2. Обработка задержек при асинхронных соединениях
Необходимо задать лимит ожидания — проще говоря, задержку — при асинхронном соединении.
Решение
Задайте задержку в URL-запросе, посылаемом классу NSURLConnection.
Обсуждение
При инстанцировании объекта типа NSURLRequest для передачи URL-соединения можно воспользоваться методом класса requestWithURL: cachePolicy: timeoutInterval:, относящимся к этому объекту, и передать желаемую длительность задержки в секундах в параметре timeoutInterval.
Например, если вы готовы не более 30 секунд дожидаться, пока загрузится содержимое главной страницы Apple (с применением синхронного соединения), создайте ваш URL таким образом:
— (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
NSString *urlAsString = @"http://www.apple.com";
NSURL *url = [NSURL URLWithString: urlAsString];
NSURLRequest *urlRequest =
[NSURLRequest
requestWithURL: url
cachePolicy: NSURLRequestReloadIgnoringLocalAndRemoteCacheData
timeoutInterval:30.0f];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection
sendAsynchronousRequest: urlRequest
queue: queue
completionHandler: ^(NSURLResponse *response,
NSData *data,
NSError *error) {
if ([data length] >0 &&
error == nil){
NSString *html = [[NSString alloc] initWithData: data
encoding: NSUTF8StringEncoding];
NSLog(@"HTML = %@", html);
}
else if ([data length] == 0 &&
error == nil){
NSLog(@"Nothing was downloaded.");
}
else if (error!= nil){
NSLog(@"Error happened = %@", error);
}
}];
self.window = [[UIWindow alloc]
initWithFrame: [[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
Что же здесь происходит? Дело в том, что среда времени исполнения пытается получить содержимое, расположенное по предоставленной ссылке. Если это удается сделать в течение заданных 30 секунд и соединение устанавливается до возникновения задержки — хорошо. В противном случае среда времени исполнения выдаст вам ошибку задержки (error) в соответствующем параметре завершающего блока.
11.3. Синхронная загрузка с применением NSURLConnection
Постановка задачи
Необходимо синхронно загрузить информацию, расположенную по имеющемуся URL.
Решение
Используйте метод класса sendSynchronousRequest: returningResponse: error:, относящийся к классу NSURLConnection. Возвращаемое значение этого метода — данные типа NSData.
Обсуждение
Пользуясь методом класса sendSynchronousRequest: returningResponse: error:, относящимся к классу NSURLConnection, можно посылать синхронный запрос к URL. А теперь внимание! Синхронные соединения не обязательно блокируют главный поток. Эти соединения блокируют актуальный поток, то есть выполняющий текущую задачу, и если этот поток не главный, то главный поток останется свободным. Если приступить к обработке глобальной параллельной очереди в GCD, а потом инициировать синхронное соединение, то вы не заблокируете главный поток.
Попробуем инициировать наше первое синхронное соединение и посмотрим, что произойдет. В данном примере мы попытаемся получить домашнюю страницу сайта Yahoo!:
— (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
NSLog(@"We are here…");
NSString *urlAsString = @"http://www.yahoo.com";
NSURL *url = [NSURL URLWithString: urlAsString];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL: url];
NSURLResponse *response = nil;
NSError *error = nil;
NSLog(@"Firing synchronous url connection…");
NSData *data = [NSURLConnection sendSynchronousRequest: urlRequest
returningResponse:&response
error:&error];
if ([data length] > 0 &&
error == nil){
NSLog(@"%lu bytes of data was returned.", (unsigned long)[data length]);
}
else if ([data length] == 0 &&
error == nil){
NSLog(@"No data was returned.");
}
else if (error!= nil){
NSLog(@"Error happened = %@", error);
}
NSLog(@"We are done.");
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
Если запустить это приложение, а потом взглянуть в окно консоли, то там окажется выведен следующий результат:
We are here…
Firing synchronous url connection…
2 52117 bytes of data was returned.
We are done.
Итак, вполне очевидно, что актуальный поток написал на консоли строку We are here…, дождался окончания соединения (поскольку это синхронное соединение, блокирующее актуальный поток), а потом вывел в окне консоли текст We are done. Теперь проведем эксперимент. Поместим то же самое синхронное соединение в глобальной параллельной очереди в GCD, то есть гарантированно обеспечим параллелизм, и посмотрим, что произойдет:
— (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
NSLog(@"We are here…");
NSString *urlAsString = @"http://www.yahoo.com";
NSLog(@"Firing synchronous url connection…");
dispatch_queue_t dispatchQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(dispatchQueue, ^(void) {
NSURL *url = [NSURL URLWithString: urlAsString];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL: url];