Книга: iOS. Приемы программирования

Обсуждение

Обсуждение

Когда приложение переходит в фоновый режим, работа его основного потока приостанавливается. Потоки, которые вы создаете в своем приложении с помощью метода класса detachNewThreadSelector: toTarget: withObject:, относящегося к классу NSThread, также приостанавливаются. Если вы пытаетесь решить долгосрочную задачу за то время, пока приложение находится в фоновом режиме, необходимо вызвать метод экземпляра beginBackgroundTaskWithExpirationHandler:, относящийся к классу UIApplication, чтобы «занять» немного времени у системы iOS. Свойство backgroundTimeRemaining класса UIApplication содержит количество секунд, которые требуются приложению для завершения той или иной задачи. Если приложение не успеет завершить долгосрочную задачу до того, как истечет отведенный временной промежуток, iOS завершит приложение. За каждым вызовом метода beginBackgroundTaskWithExpirationHandler: должен следовать соответствующий вызов метода endBackgroundTask: (другой метод экземпляра UIApplication). Иными словами, если вы просите у iOS дополнительное время на завершение задачи, то должны и сообщить iOS, когда закончите эту задачу. Когда это будет сделано и окажется, что больше нет задач, которые следовало бы выполнить в фоновом режиме, ваше приложение перейдет в фоновый режим целиком и все его потоки будут приостановлены.

Когда приложение работает на переднем плане, свойство backgroundTimeRemaining класса UIApplication равно константе DBL_MAX, означающей максимальную величину, которая может содержаться в значении типа double (число с двойной точностью). В данном случае целочисленное значение, равное такому двойному вещественному числу, обычно составляет –1. После того как у iOS запрашивается дополнительное время перед полной приостановкой приложения, в этом свойстве указывается количество секунд, требуемое программе для завершения задачи (или всех текущих задач).

Можно вызывать в приложении метод beginBackgroundTaskWithExpirationHandler: столько раз, сколько потребуется. Но важно учитывать, что, когда iOS возвращает в этом методе вашей программе метку (токен) или идентификатор задачи, вы должны вызвать метод endBackgroundTask:, и сделать это сразу же, как программа закончит выполнять задачу. Если не сделать этого, операционная система iOS может завершить приложение.

Если приложение работает в фоновом режиме, то не предполагается, что оно останется полностью функциональным и сможет обрабатывать «тяжелые» данные. Действительно, такие приложения рассчитаны лишь на завершение долгосрочных задач.

В качестве примера можно привести приложение, направившее вызов к API веб-службы и еще не получившее с сервера ответ от этого API. На время ожидания приложение будет отправлено в фоновый режим, и приложение сможет запросить дополнительное время для получения ответа с сервера. Как только ответ будет получен, приложение должно сохранить свое состояние, а потом отметить факт завершения задачи, вызвав метод экземпляра endBackgroundTask:, относящийся к классу UIApplication.

Рассмотрим пример. Начнем с того, что определим в делегате приложения свойство типа UIBackgroundTaskIdentifier. Кроме того, определим таймер типа NSTimer, который будет использоваться при ежесекундном выводе сообщений на консоль, после того как приложение уйдет в фоновый режим:

#import «AppDelegate.h»
@interface AppDelegate ()
@property (nonatomic, unsafe_unretained)
UIBackgroundTaskIdentifier backgroundTaskIdentifier;
@property (nonatomic, strong) NSTimer *myTimer;
@end
@implementation AppDelegate
<# Остаток вашего кода находится здесь #>
Теперь создадим таймер и назначим время перехода приложения в фоновый режим:
— (BOOL) isMultitaskingSupported{
BOOL result = NO;
if ([[UIDevice currentDevice]
respondsToSelector:@selector(isMultitaskingSupported)]){
result = [[UIDevice currentDevice] isMultitaskingSupported];
}
return result;
}
— (void) timerMethod:(NSTimer *)paramSender{
NSTimeInterval backgroundTimeRemaining =
[[UIApplication sharedApplication] backgroundTimeRemaining];
if (backgroundTimeRemaining == DBL_MAX){
NSLog(@"Background Time Remaining = Undetermined");
} else {
NSLog(@"Background Time Remaining = %.02f Seconds",
backgroundTimeRemaining);
}
}
— (void)applicationDidEnterBackground:(UIApplication *)application{
if ([self isMultitaskingSupported] == NO){
return;
}
self.myTimer =
[NSTimer scheduledTimerWithTimeInterval:1.0f
target: self
selector:@selector(timerMethod:)
userInfo: nil
repeats: YES];
self.backgroundTaskIdentifier =
[application beginBackgroundTaskWithExpirationHandler: ^(void) {
[self endBackgroundTask];
}];
}

Как видите, в обработчике завершения (Completion Handler) фоновой задачи мы вызываем метод endBackgroundTask делегата приложения. Этот метод написали мы сами, и выглядит он так:

— (void) endBackgroundTask{
dispatch_queue_t mainQueue = dispatch_get_main_queue();
__weak AppDelegate *weakSelf = self;
dispatch_async(mainQueue, ^(void) {
AppDelegate
*strongSelf = weakSelf;
if (strongSelf!= nil){
[strongSelf.myTimer invalidate];
[[UIApplication sharedApplication]
endBackgroundTask: self.backgroundTaskIdentifier];
strongSelf.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
}
});
}

Есть еще пара моментов, которые нужно дополнительно уладить после завершения долгосрочной задачи:

• завершить все потоки и таймеры независимо от того, являются они таймерами из фреймворка Core Foundation или созданы с помощью GCD;

• завершить фоновую задачу, вызвав метод endBackgroundTask: класса UIApplication;

• пометить задачу как завершенную, присвоив значение UIBackgroundTaskInvalid идентификаторам задачи.

И последнее, но немаловажное. Если приложение выходит в приоритетный режим, а долгосрочная задача все еще не завершена, необходимо гарантированно от нее избавиться:

— (void)applicationWillEnterForeground:(UIApplication *)application{
if (self.backgroundTaskIdentifier!= UIBackgroundTaskInvalid){
[self endBackgroundTask];
}
}

В нашем примере, как только приложение переходит в фоновый режим, мы запрашиваем дополнительное время на завершение долгосрочной задачи (в данном случае, например, для выполнения кода нашего таймера). В то время мы регулярно считываем значение свойства backgroundTimeRemaining экземпляра класса UIApplication и выводим найденное значение на консоль. В методе экземпляра beginBackgroundTaskWithExpirationHandler:, который относится к классу UIApplication, мы записали код, который будет выполнен прямо перед тем, как закончится дополнительное время, выделенное приложению на выполнение долгосрочной задачи. (Как правило, это происходит за 5–10 секунд до истечения времени, выделенного на выполнение задачи.) Здесь мы можем просто завершить задачу, вызвав метод экземпляра endBackgroundTask:, относящийся к классу UIApplication.

Если приложение перешло в фоновый режим и запросило у операционной системы дополнительное время на исполнение кода еще до того, как отведенное время истекло, пользователь может «оживить» приложение и вернуть его в приоритетный режим. Если до этого вы приказали исполнять долгосрочную задачу в фоновом режиме и как раз для этого приложение было переведено в фоновый режим, то нужно завершить долгосрочную задачу с помощью метода экземпляра endBackgroundTask:, относящегося к классу UIApplication.

Оглавление книги


Генерация: 0.055. Запросов К БД/Cache: 0 / 0
поделиться
Вверх Вниз