iOS. Приемы программирования Нахавандипур Вандад
[self pauseGameEngine];
}
— (void)applicationDidBecomeActive:(UIApplication *)application{
[self resumeGameEngine];
}
Теперь все просто. Как только наше приложение уйдет в фоновый режим, мы сохраним состояние этой программы, а когда она вернется в приоритетный режим — вновь загрузим это состояние:
— (void)applicationDidEnterBackground:(UIApplication *)application{
[self saveUserScore];
[self saveLevelToDisk];
[self pauseGameEngine];
}
— (void)applicationWillEnterForeground:(UIApplication *)application{
[self loadUserScore];
[self loadLevelFromDisk];
[self resumeGameEngine];
}
Разумеется, не всякое приложение — это игра. Но описанными приемами можно пользоваться для загрузки и сохранения состояния приложений в многозадачной среде iOS.
См. также
Раздел 14.2.
14.7. Управление сетевыми соединениями в фоновом режиме
Постановка задачи
Вы применяете экземпляры класса NSURLConnection для получения данных с веб-сервера и отправки информации на сервер. Возникает вопрос: как гарантировать работу ваших приложений в многозадачной среде iOS, надежно застраховавшись от сбоев в соединениях.
Решение
Следует обеспечить обработку ошибок соединения в блоковых объектах, передаваемых вашим объектам соединений.
Обсуждение
При работе с приложениями, которые используют класс NSURLConnection, но, уходя в фоновый режим, не запрашивают у iOS дополнительного времени, обращаться с соединениями не составляет никакого труда. Рассмотрим на примере, как будет действовать асинхронное соединение, если приложение сначала уходит в фоновый режим, а потом возвращается в приоритетный. Итак, сделаем запрос на асинхронное соединение, чтобы получить контент, расположенный по определенному URL (например, на домашней странице Apple):
— (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
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){
/* Данные вернулись. */
}
else if ([data length] == 0 &&
error!= nil){
/* Никаких данных от сервера не пришло. */
}
else if (error!= nil){
/* Произошла ошибка. Ее обязательно нужно правильно обработать. */
}
}];
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
В этом примере целесообразно заменить URL домашней страницы Apple другим интернет-адресом, по которому расположен какой-нибудь крупный файл. Причина заключается в том, что, пока ваше приложение будет скачивать большой файл, у вас будет больше времени поэкспериментировать с приложением — отправить его в фоновый режим, а потом вернуть в приоритетный. Если же у вас довольно быстрое соединение с Интернетом, а вы загружаете всего одну страницу Apple, то вполне вероятно, что на это уйдет всего 1–2 секунды.
Будучи в приоритетном режиме, наше приложение продолжит загрузку файла. В ходе загрузки пользователь может нажать кнопку Home (Домой) и отправить приложение в фоновый режим. И тогда вы увидите настоящее волшебство! iOS автоматически приостановит процесс загрузки без всякого вашего вмешательства. Когда же пользователь вновь переведет программу в приоритетный режим, загрузка возобновится и вам не придется писать ни единой строки кода для обработки многозадачности в такой ситуации.
Теперь рассмотрим, что происходит при синхронных соединениях. Как только наше приложение запустится, попробуем скачать очень большой файл через главный поток (крайне порочная практика, никогда так не делайте в боевом проекте!):
— (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
/* Заменяем этот URL ссылкой на крупный файл. */
NSString *urlAsString = @"http://www.apple.com";
NSURL *url = [NSURL URLWithString: urlAsString];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL: url];
NSError *error = nil;
NSData *connectionData =
[NSURLConnection sendSynchronousRequest: urlRequest
returningResponse: nil
error:&error];
if ([connectionData length] > 0 &&
error == nil){
}
else if ([connectionData length] == 0 &&
error == nil){
}
else if (error!= nil){
}
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
Если вы запустите это приложение и переведете его в фоновый режим, то заметите, что на задний план отходит только графический пользовательский интерфейс, но вот ядро приложения никуда из приоритетного режима не уходит и нужные сообщения делегата — applicationWillResignActive: и applicationDidEnterBackground: — так и не будут получены. Я проводил такой опыт на iPhone.
Проблема такого решения заключается в том, что для синхронной загрузки файлов мы потребляем ту долю компьютерного времени, которая отводится главному потоку. Чтобы избавиться от проблемы, мы можем либо асинхронно загружать файлы в главном потоке, как было продемонстрировано ранее, либо синхронно загружать их в отдельных потоках.
Вернемся к предыдущему примеру кода. Если мы будем загружать тот же большой файл синхронно, в глобальной параллельной очереди, то с уходом приложения в фоновый режим соединение будет приостановлено и возобновится только после возвращения программы в приоритетный режим:
— (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
dispatch_queue_t dispatchQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(dispatchQueue, ^(void) {
/* Заменяем этот URL ссылкой на крупный файл. */
NSString *urlAsString = @"http://www.apple.com";
NSURL *url = [NSURL URLWithString: urlAsString];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL: url];
NSError *error = nil;
NSData *connectionData = [NSURLConnection
sendSynchronousRequest: urlRequest
returningResponse: nil
error:&error];
if ([connectionData length] > 0 &&
error == nil){
}
else if ([connectionData length] == 0 &&
error == nil){
}
else if (error!= nil){
}
});
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
См. также
Раздел 14.2.
14.8. Отказ от многозадачности
Постановка задачи
Требуется исключить использование в вашем приложении многозадачности.
Решение
Добавьте в главный файл. plist приложения ключ UIApplicationExitsOnSuspend и задайте ему значение true:
<# Некоторые ключи и значения #>
<key>UIApplicationExitsOnSuspend</key>
<true/>
<# Остальные ключи и значения #>
Обсуждение
Иногда бывает необходимо исключить возможность многозадачности в приложениях для iOS. (Хотя я настоятельно рекомендую разрабатывать программы с поддержкой многозадачности.) В таких случаях нужно добавить ключ UIApplicationExitsOnSuspend в главный файл. plist приложения. Устройства с самыми новыми версиями системы iOS понимают это значение, и операционная система будет завершать приложения, не переводя их в фоновый режим, если в файле. plist того или иного приложения этот ключ будет иметь значение true. В более ранних версиях iOS, где не поддерживается многозадачность, операционная система будет просто игнорировать это значение.
Когда подобное приложение работает в новой версии iOS, оно получит следующие сообщения делегата.
1. application: didFinishLaunchingWithOptions:.
2. applicationDidBecomeActive:.
Если пользователь нажмет на устройстве кнопку Home (Домой), то делегату будут отправлены следующие сообщения.
1. applicationDidEnterBackground:.
2. applicationWillTerminate:.
Глава 15. Уведомления
15.0. Введение
Уведомления — это объекты, несущие определенную информацию, которая может передаваться множеству получателей методом широковещания. Уведомления очень удобны для разделения работы на относительно самостоятельные фрагменты кода, но при злоупотреблении ими ситуация легко может выйти из-под контроля. Следует понимать границы возможностей при работе с уведомлениями. В этой главе мы подробно поговорим об использовании уведомлений и узнаем, когда лучше обходиться без них.
В iOS доступны уведомления трех типов.
• Обычное уведомление (экземпляр класса NSNotification). Это обычное уведомление. Программа может широковещательно передавать его любым получателям в рамках приложения. iOS также широковещательно направляет вашему приложению уведомления такого типа, пока приложение находится в приоритетном режиме. Таким образом приложение получает информацию о различных системных событиях, происходящих во время его работы, например о выводе виртуальной клавиатуры на экран и ее уходе с экрана. Эти уведомления хорошо подходят для ослабления связанности кода и позволяют аккуратно отделять друг от друга различные компоненты сложного iOS-приложения.
Локальное уведомление (экземпляр класса UILocalNotification). Это уведомление, которое должно быть доставлено вашему приложению в определенный момент времени. Приложение сможет его получить, даже если находится в фоновом режиме или не работает вообще. Если приложение не работало, но получило такое уведомление, то оно запускается. Как правило, вы назначаете локальное уведомление, если хотите гарантированно разбудить приложение (предполагается, что пользователь разрешил вам такое действие, подробнее об этом — в дальнейшем) в определенный момент дня.
Пуш-уведомление. Такое уведомление отсылается на устройство iOS с сервера. Это уведомление выполняется по инициативе сервера, поэтому приложению не приходится опрашивать сервер на наличие таких уведомлений. iOS поддерживает постоянное соединение с серверами APNS (службы Apple для обеспечения пуш-уведомлений). Как только появляется новое пуш-уведомление, iOS обрабатывает сообщение и отсылает его тому приложению, которому это уведомление предназначалось.
Далее мы будем называть обычные уведомления просто уведомлениями. Слово «обычный» в данном контексте избыточно.
Особенность локальных уведомлений заключается в том, что они видны пользователю и пользователь может совершать над ними те или иные действия. iOS зафиксирует действие пользователя, после чего прикажет вашему приложению обработать это действие. В то же время уведомления являются невидимыми элементами. В приложении их можно распространять широковещательным способом, и приложение обязано обрабатывать эти уведомления. Пользователь не обязательно должен быть непосредственно вовлечен в этот процесс, если вы сами не потребуете от него каких-либо действий в результате получения и обработки уведомления. Например, приложение может послать уведомление в другую часть этого же уведомления. По получении такого уведомления в другой части вашего приложения генерируется диалоговое окно с предупреждением. После этого уже требуется вмешательство пользователя: он должен ознакомиться с этим окном и, например, нажать в нем кнопку ОК, чтобы закрыть его. Такое непрямое вовлечение пользователя очень отличается от его непосредственного участия, которое требуется при работе с обычными уведомлениями.
Уведомления — важнейшая составляющая операционных систем iOS и OS X. iOS выдает уведомления, действующие в масштабах всей системы. Эти уведомления адресуются всем приложениям в системе, которые их слушают, и сами приложения также могут отсылать уведомления. Такое уведомление, действующее в масштабах всей системы (также называемое распределенным), может выдаваться только самой системой iOS.
Уведомление — это простая сущность, представленная в iOS SDK классом NSNotification. Уведомление отсылается объектом и может нести информацию. Объект, отсылающий уведомление, «представляет себя» центру уведомления в момент самой отправки уведомления. Затем получатель уведомления может «справиться» об отправителе по его имени класса для получения более подробной информации об этом объекте. Объект-отправитель называется объектом уведомления. Кроме того, уведомление может включать в себя словарь с пользовательской информацией. Это словарная структура данных, которая может нести дополнительную информацию об уведомлении. Если словарь не предоставляется, то этот параметр равен nil.
15.1. Отправка уведомлений
Постановка задачи
Требуется разграничить части вашего приложения и отправить уведомление, которое может быть подхвачено другим компонентом приложения.
Решение
Создайте экземпляр класса NSNotification и широковещательно передайте его вашему приложению, воспользовавшись методом класса postNotification:. Вы можете получить экземпляр центра уведомлений, воспользовавшись его методом класса defaultCenter, вот так:
#import «AppDelegate.h»
NSString *const kNotificationName = @"NotificationNameGoesHere";
@implementation AppDelegate
— (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
NSNotification *notification = [NSNotification
notificationWithName: kNotificationName
object: self
userInfo:@{@"Key 1": @"Value 1",
@"Key 2": @2}];
[[NSNotificationCenter defaultCenter] postNotification: notification];
self.window = [[UIWindow alloc]
initWithFrame: [[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
Обсуждение
Объект уведомления инкапсулируется в экземпляр класса NSNotification. Сам по себе объект уведомления практически ничего не представляет. Чтобы он был полезен, его нужно послать приложению с помощью центра уведомлений. Объект уведомления имеет три важных свойства.
• Имя. Это строка. Когда получатель начинает слушать уведомления, он должен указать имя интересующего его уведомления, как будет показано далее в этой главе. Если вы отправляете уведомление в созданный вами класс, убедитесь, что имя уведомления хорошо документировано. Еще лучше импортировать символ этой строки в файл заголовка. Подобный пример мы рассмотрим чуть позже в этом разделе.
Объект-отправитель. По желанию вы можете указать объект, являющийся отправителем уведомления. Обычно в таком качестве задается self. Но зачем же нужно указывать отправитель уведомления? Эта информация полезна для тех компонентов приложения, которые слушают уведомления. Допустим, в одном из ваших классов вы отправляете уведомление с именем MyNotification, а другой класс приложения отсылает уведомление с точно таким же именем. Когда элемент начинает слушать уведомление с именем MyNotification, получатель может указать, из какого источника ожидается интересующее его уведомление. Так, получатель может указать, что ему требуются все уведомления с именем MyNotification, поступающие от конкретного объекта, но не интересуют одноименные уведомления, приходящие от другого объекта. Таким образом, получатель действительно контролирует ситуацию. Хотя вы даже и можете при отправке уведомления указать вместо объекта-получателя nil, гораздо целесообразнее задавать данному свойству self, то есть имя объекта, отправляющего уведомление.
Словарь с пользовательской информацией. Это словарный объект, который вы можете прикреплять к объекту уведомления. Затем получатель может считывать этот словарь, когда получает уведомление. Можно сказать, что в этом параметре удобно передавать получателям вашего уведомления дополнительную информацию.
См. также
Раздел 15.0.
15.2. Слушание уведомлений и реагирование на них
Постановка задачи
Требуется отреагировать на уведомление, посылаемое либо вашим приложением, либо системой.
Решение
Слушайте интересующее вас уведомление путем вызова метода addObserver: selector: name: object: стандартного центра уведомлений. Этот метод имеет следующие параметры:
• addObserver — объект, который должен отслеживать заданное уведомление. Поэтому, если речь идет о текущем классе, задайте здесь self, чтобы указать на актуальный экземпляр вашего класса;
• selector — селектор, который будет получать уведомление. Этот селектор должен иметь один параметр типа NSNotification;
• name — имя уведомления, которое вы хотите слушать;
• object — объект, который должен прислать вам уведомление. Например, если одноименные уведомления поступают сразу от двух объектов, то вы можете сузить круг интересующих вас уведомлений и слушать только те из них, которые приходят от объекта A, игнорируя при этом приходящие от объекта B.
Если вы больше не хотите получать уведомления, выполните метод экземпляра removeObserver:, относящийся к классу NSNotificationCenter. Это должно делаться лишь при условии, что центр уведомлений удерживает экземпляры объектов-слушателей. Если центр уведомлений продолжает удерживать экземпляр вашего класса после того, как он был высвобожден, могут возникать утечки памяти и ошибки. Поэтому убедитесь в том, что своевременно удаляете объект из списка наблюдателей.
Обсуждение
Вся эта теория станет значительно более понятной, если пояснить на примере. Мы собираемся создать класс Person и добавить к нему два свойства: имя и фамилию. Оба этих свойства будут относиться к типу NSString. Затем в делегате нашего приложения мы собираемся инстанцировать объект типа Person. Но не будем задавать имя и фамилию этой персоны, а отошлем в центр уведомлений само уведомление и его пользовательский словарь. В этом пользовательском словаре уведомления запишем имя и фамилию как элементы типа NSString. В методе инициализации класса Person мы собираемся слушать уведомление, которое приходит от делегата приложения. Затем извлечем имя и фамилию из пользовательского словаря и зададим эти значения для соответствующих свойств объекта-персоны.
Вот заголовочный файл делегата нашего приложения:
#import <UIKit/UIKit.h>
/* Имя уведомления, которое мы собираемся послать */
extern NSString *const kSetPersonInfoNotification;
/* Ключ имени в словаре пользовательской информации уведомления */
extern NSString *const kSetPersonInfoKeyFirstName;
/* Ключ фамилии в словаре пользовательской информации уведомления */
extern NSString *const kSetPersonInfoKeyLastName;
@interface AppDelegate: UIResponder <UIApplicationDelegate>
@property (nonatomic, strong) UIWindow *window;
@end
А вот реализация делегата нашего приложения:
#import «AppDelegate.h»
#import «Person.h»