Ожидается ли запуск NSTimer, когда приложение установлено на задний план?
Я вообще не понимаю, но NSTimer
в моем приложении определенно работает в фоновом режиме. У меня есть NSLog
в методе, запускаемом таймером, и он регистрируется, пока он работает в фоновом режиме. Это на iPhone 4 с iOS 4.2.1. Я объявил поддержку фона местоположения в Info.plist.
Я читаю документы и многие обсуждения здесь и в других местах, и это не должно быть возможным. Это ошибка iOS? Или недокументированная особенность? Я не хочу использовать его и узнаю в ближайшем будущем, например, с выходом iOS 4.3, что Apple молча "исправила" его, и приложение не будет работать.
Кто-нибудь знает больше об этом?
Ответы
Ответ 1
NSTimer
будет срабатывать всякий раз, когда запускается основная runloop. Apple не делает promises, что я знаю о нечеловеческих таймерах или для предотвращения запуска основной runloop. Это ваша обязанность отключать ваши таймеры и выделять ресурсы при переходе на задний план. Apple не собирается делать это за вас. Тем не менее, они могут убить вас за запуск, если вы не должны или используете слишком много секунд.
В системе много дыр, которые позволят запускать приложение, если оно не разрешено. Это было бы очень дорого для ОС, чтобы предотвратить это. Но вы не можете полагаться на это.
Ответ 2
Вы можете иметь огонь по таймеру в режиме фонового исполнения. Есть несколько трюков:
- Вам нужно выбрать фоновое выполнение с помощью
beginBackgroundTaskWithExpirationHandler
.
- Если вы создаете NSTimer в фоновом потоке, вам нужно вручную добавить его в mainRunLoop.
- (void)viewDidLoad
{
// Avoid a retain cycle
__weak ViewController * weakSelf = self;
// Declare the start of a background task
// If you do not do this then the mainRunLoop will stop
// firing when the application enters the background
self.backgroundTaskIdentifier =
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundIdentifier];
}];
// Make sure you end the background task when you no longer need background execution:
// [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Since we are not on the main run loop this will NOT work:
[NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:@selector(timerDidFire:)
userInfo:nil
repeats:YES];
// This is because the |scheduledTimerWithTimeInterval| uses
// [NSRunLoop currentRunLoop] which will return a new background run loop
// which will not be currently running.
// Instead do this:
NSTimer * timer =
[NSTimer timerWithTimeInterval:0.5
target:weakSelf
selector:@selector(timerDidFire:)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer
forMode:NSDefaultRunLoopMode];
// or use |NSRunLoopCommonModes| if you want the timer to fire while scrolling
});
}
- (void) timerDidFire:(NSTimer *)timer
{
// This method might be called when the application is in the background.
// Ensure you do not do anything that will trigger the GPU (e.g. animations)
// See: http://developer.apple.com/library/ios/DOCUMENTATION/iPhone/Conceptual/iPhoneOSProgrammingGuide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html#//apple_ref/doc/uid/TP40007072-CH4-SW47
NSLog(@"Timer did fire");
}
Примечания
- Приложения только получают ~ 10 минут выполнения фона - после этого таймер прекратит стрельбу.
- Начиная с iOS 7, когда устройство заблокировано, он почти мгновенно приостанавливает приложение переднего плана. Таймер не срабатывает после блокировки приложения iOS 7.