我的NSTimers和背景选择器一直存在问题。它让我疯狂,需要很长时间来尝试每个调整。为了保持我的理智和未来几代可可程序员的理智,我提出了这个问题:
是否有一种绝对100%可靠的方法可以在以后的某个时间点启动计划的长期计时器,无论是从后台线程,主线程等调用它?
似乎我一直在为使用NSTimers的大多数类一遍又一遍地解决同样的问题。它们在短期测试期间工作,让我们说我将计时器设置为通过后台线程在10秒内触发。它有效,因为还有一个运行循环运行。但是,一旦我把火的时间改为我真正想要的,比如15-30分钟,就会有沉默。运行循环消失了,我不知道如何处理这种情况。没有任何事情发生,几天后我发现了这样的错误,一旦我已经忘记了哪个计时器将对此负责。
目前我正在与选择者一起做一些非常非常丑陋的舞蹈,例如这里的测试方法(它似乎适用于10分钟的计时器):
//this is a test method to simulate a background task requesting a timer
[self performSelectorInBackground:@selector(backgroundReminderLongTermTest:) withObject:nil];
//this is a method similar to the one that the background thread would be trying to invoke
-(void)backgroundReminderLongTermTest:(id)sender
{
[self performSelectorOnMainThread:@selector(backgroundReminderFromMainThread:) withObject:nil waitUntilDone:NO];
}
//this is a wrapper for the background method, I want the timer to be added to a thread with a run loop already established and running
-(void)backgroundReminderFromMainThread:(id)sender
{
[playTimers addObject:[NSTimer scheduledTimerWithTimeInterval:1800 target:self selector:@selector(start:) userInfo:nil repeats:NO]];
}
我喜欢不必担心使用预定的计时器创建一个火灾日期对象的便利,但是我应该忘记它们并使用具有特定火灾日期的计时器吗?似乎scheduleTimer适用于短期任务,当运行循环已经存在时,但我在应用程序执行期间根本看不到这种错误。有一次,似乎定时器正常射击,但是稍后它们会完全停止射击。
感谢您的帮助或澄清。 我正在寻找一种方法来安排计时器,而不必担心每次我需要安排计时器时是否存在运行循环。我想确保只要应用程序正在运行,我通过此方法安排的计时器将在未来的可预测点触发。
答案 0 :(得分:4)
NSTimers的一个无数问题是它们的运行循环依赖性。每个线程都有一个运行循环。如果您在后台线程上安排计时器,它将在该线程的运行循环上进行调度。如果该线程是短暂的,通常是哪个后台线程,那么该计时器将悄然死亡。
解决方案是保证计时器在计时器触发时将处于活动状态的线程上运行。根据我的经验,这些专用背景计时器的最佳方法是根本不使用NSTimer,而是使用GCD计时器。比我编写GCD动力定时器的人更好。我个人更喜欢Mike Ash's文章和实现,附带解释。
答案 1 :(得分:2)
改为使用local notification。
答案 2 :(得分:1)
只要你依赖scheduledTimerWithTimeInterval:...
,你就无法达到你想要的效果:
计时器将始终与调用线程的运行循环绑定。
如果在该消息调用之前没有与该线程关联的运行循环,那么当方法返回时肯定会有一个运行循环,因为-[NSRunLoop currentRunLoop]
会在必要时创建一个运行循环。
你可以做什么,如果你不喜欢用于创建计时器的其他API,则在NSTimer
上提供一个类别,它负责所有的日程安排,所以并且您可以在其他项目中重复使用。
以下是可能类别的示例:
#pragma mark - setting up a timer:
+ (NSTimer *)yourPrefix_mainLoopScheduledTimerWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat
{
NSTimer *timer = [self yourPrefix_timerWithTimeInterval:interval target:target selector:selector userInfo:userInfo repeats:shouldRepeat];
void (^scheduler)() = ^{
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
};
if ([NSThread isMainThread]) {
scheduler();
} else {
// you should really be able to rely on the fact, that the timer is ready to roll, when this method returns
dispatch_sync(dispatch_get_main_queue(), scheduler);
}
return timer;
}
// this is just a convenience for the times where you actually want an _unscheduled_ timer
+ (NSTimer *)yourPrefix_timerWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat
{
NSDate *fireDate = [NSDate dateWithTimeIntervalSinceNow:interval];
NSTimer *timer = [[self alloc] initWithFireDate:fireDate interval:interval target:target selector:selector userInfo:userInfo repeats:shouldRepeat];
return [timer autorelease];
}
#pragma mark - tearing it down:
- (void)yourPrefix_invalidateMainLoopTimer
{
[self yourPrefix_invalidateMainLoopTimerAsynchronous:NO];
}
- (void)yourPrefix_invalidateMainLoopTimerAsynchronous:(BOOL)returnsImmediately
{
void (^invalidator)() = ^{
[self invalidate];
};
dispatch_queue_t mainQueue = dispatch_get_main_queue();
if (returnsImmediately) {
dispatch_async(mainQueue, invalidator);
return;
}
if (![NSThread isMainThread]) {
dispatch_sync(mainQueue, invalidator);
return;
}
invalidator();
}
在使用dispatch_sync
之前请注意线程检查,因为......
dispatch_sync
讨论
[...]调用此函数并定位当前队列会导致死锁。
(来自The GCD Reference - 强调我的)